From 0d2c890da11b23948d229070c89050f911fe8852 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 13:29:48 -0400 Subject: [PATCH 001/245] [deps] UI Foundation: Update @types/react to v18.3.20 (#14464) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0db144090b9..bdf9c3a9d26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11820,9 +11820,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.1.tgz", - "integrity": "sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==", + "version": "18.3.20", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.20.tgz", + "integrity": "sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 7c1873090c1..6fe463ac387 100644 --- a/package.json +++ b/package.json @@ -212,7 +212,7 @@ }, "react": "18.3.1", "react-dom": "18.3.1", - "@types/react": "18.3.1", + "@types/react": "18.3.20", "replacestream": "4.0.3" }, "lint-staged": { From 624dafa431f9bf783529e11f35cd665f276eb664 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 13:51:15 -0400 Subject: [PATCH 002/245] [deps] UI Foundation: Update angular-cli monorepo to v18.2.19 (#14465) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 718 +++++++++++++++++++++------------------------- package.json | 6 +- 2 files changed, 333 insertions(+), 391 deletions(-) diff --git a/package-lock.json b/package-lock.json index bdf9c3a9d26..1e7e0741cd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,9 +74,9 @@ "zxcvbn": "4.4.2" }, "devDependencies": { - "@angular-devkit/build-angular": "18.2.12", + "@angular-devkit/build-angular": "18.2.19", "@angular-eslint/schematics": "18.4.3", - "@angular/cli": "18.2.12", + "@angular/cli": "18.2.19", "@angular/compiler-cli": "18.2.13", "@babel/core": "7.24.9", "@babel/preset-env": "7.24.8", @@ -84,7 +84,7 @@ "@electron/notarize": "2.5.0", "@electron/rebuild": "3.7.2", "@lit-labs/signals": "0.1.2", - "@ngtools/webpack": "18.2.12", + "@ngtools/webpack": "18.2.19", "@storybook/addon-a11y": "8.6.12", "@storybook/addon-actions": "8.6.12", "@storybook/addon-designs": "8.2.1", @@ -414,29 +414,28 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.12.tgz", - "integrity": "sha512-quVUi7eqTq9OHumQFNl9Y8t2opm8miu4rlYnuF6rbujmmBDvdUvR6trFChueRczl2p5HWqTOr6NPoDGQm8AyNw==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.19.tgz", + "integrity": "sha512-xwY7v+nGE7TXOc4pgY6u57bLzIPSHuecosYr3TiWHAl9iEcKHzkCCFKsLZyunohHmq/i1uA6g3cC6iwp2xNYyg==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.12", - "@angular-devkit/build-webpack": "0.1802.12", - "@angular-devkit/core": "18.2.12", - "@angular/build": "18.2.12", - "@babel/core": "7.25.2", - "@babel/generator": "7.25.0", - "@babel/helper-annotate-as-pure": "7.24.7", + "@angular-devkit/architect": "0.1802.19", + "@angular-devkit/build-webpack": "0.1802.19", + "@angular-devkit/core": "18.2.19", + "@angular/build": "18.2.19", + "@babel/core": "7.26.10", + "@babel/generator": "7.26.10", + "@babel/helper-annotate-as-pure": "7.25.9", "@babel/helper-split-export-declaration": "7.24.7", - "@babel/plugin-transform-async-generator-functions": "7.25.0", - "@babel/plugin-transform-async-to-generator": "7.24.7", - "@babel/plugin-transform-runtime": "7.24.7", - "@babel/preset-env": "7.25.3", - "@babel/runtime": "7.25.0", + "@babel/plugin-transform-async-generator-functions": "7.26.8", + "@babel/plugin-transform-async-to-generator": "7.25.9", + "@babel/plugin-transform-runtime": "7.26.10", + "@babel/preset-env": "7.26.9", + "@babel/runtime": "7.26.10", "@discoveryjs/json-ext": "0.6.1", - "@ngtools/webpack": "18.2.12", - "@vitejs/plugin-basic-ssl": "1.1.0", + "@ngtools/webpack": "18.2.19", "ansi-colors": "4.1.3", "autoprefixer": "10.4.20", "babel-loader": "9.1.3", @@ -446,7 +445,7 @@ "css-loader": "7.1.2", "esbuild-wasm": "0.23.0", "fast-glob": "3.3.2", - "http-proxy-middleware": "3.0.3", + "http-proxy-middleware": "3.0.5", "https-proxy-agent": "7.0.5", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", @@ -475,7 +474,6 @@ "terser": "5.31.6", "tree-kill": "1.2.2", "tslib": "2.6.3", - "vite": "5.4.6", "watchpack": "2.4.1", "webpack": "5.94.0", "webpack-dev-middleware": "7.4.2", @@ -543,13 +541,13 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { - "version": "0.1802.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.12.tgz", - "integrity": "sha512-bepVb2/GtJppYKaeW8yTGE6egmoWZ7zagFDsmBdbF+BYp+HmeoPsclARcdryBPVq68zedyTRdvhWSUTbw1AYuw==", + "version": "0.1802.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz", + "integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.12", + "@angular-devkit/core": "18.2.19", "rxjs": "7.8.1" }, "engines": { @@ -559,9 +557,9 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.12.tgz", - "integrity": "sha512-NtB6ypsaDyPE6/fqWOdfTmACs+yK5RqfH5tStEzWFeeDsIEDYKsJ06ypuRep7qTjYus5Rmttk0Ds+cFgz8JdUQ==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -587,22 +585,22 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -627,95 +625,110 @@ "semver": "bin/semver.js" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@babel/preset-env": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz", - "integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==", + "node_modules/@angular-devkit/build-angular/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-option": "^7.24.8", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/preset-env": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz", + "integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.8", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.7", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-async-generator-functions": "^7.25.0", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoped-functions": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.24.7", - "@babel/plugin-transform-class-static-block": "^7.24.7", - "@babel/plugin-transform-classes": "^7.25.0", - "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-dotall-regex": "^7.24.7", - "@babel/plugin-transform-duplicate-keys": "^7.24.7", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", - "@babel/plugin-transform-dynamic-import": "^7.24.7", - "@babel/plugin-transform-exponentiation-operator": "^7.24.7", - "@babel/plugin-transform-export-namespace-from": "^7.24.7", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.25.1", - "@babel/plugin-transform-json-strings": "^7.24.7", - "@babel/plugin-transform-literals": "^7.25.2", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-member-expression-literals": "^7.24.7", - "@babel/plugin-transform-modules-amd": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-modules-systemjs": "^7.25.0", - "@babel/plugin-transform-modules-umd": "^7.24.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-new-target": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-numeric-separator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-object-super": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-property-literals": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-reserved-words": "^7.24.7", - "@babel/plugin-transform-shorthand-properties": "^7.24.7", - "@babel/plugin-transform-spread": "^7.24.7", - "@babel/plugin-transform-sticky-regex": "^7.24.7", - "@babel/plugin-transform-template-literals": "^7.24.7", - "@babel/plugin-transform-typeof-symbol": "^7.24.8", - "@babel/plugin-transform-unicode-escapes": "^7.24.7", - "@babel/plugin-transform-unicode-property-regex": "^7.24.7", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.26.8", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.26.5", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.26.3", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.26.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.26.3", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.26.8", + "@babel/plugin-transform-typeof-symbol": "^7.26.7", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-corejs3": "^0.11.0", "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.37.1", + "core-js-compat": "^3.40.0", "semver": "^6.3.1" }, "engines": { @@ -827,6 +840,20 @@ "webpack": ">=5" } }, + "node_modules/@angular-devkit/build-angular/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/browserslist": { "version": "4.24.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", @@ -1345,9 +1372,9 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", - "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1416,13 +1443,13 @@ } }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1802.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.12.tgz", - "integrity": "sha512-0Z3fdbZVRnjYWE2/VYyfy+uieY+6YZyEp4ylzklVkc+fmLNsnz4Zw6cK1LzzcBqAwKIyh1IdW20Cg7o8b0sONA==", + "version": "0.1802.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.19.tgz", + "integrity": "sha512-axz1Sasn+c+GJpJexBL+B3Rh1w3wJrQq8k8gkniodjJ594p4ti2qGk7i9Tj8A4cXx5fGY+EpuZvKfI/9Tr7QwA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1802.12", + "@angular-devkit/architect": "0.1802.19", "rxjs": "7.8.1" }, "engines": { @@ -1436,13 +1463,13 @@ } }, "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { - "version": "0.1802.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.12.tgz", - "integrity": "sha512-bepVb2/GtJppYKaeW8yTGE6egmoWZ7zagFDsmBdbF+BYp+HmeoPsclARcdryBPVq68zedyTRdvhWSUTbw1AYuw==", + "version": "0.1802.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz", + "integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.12", + "@angular-devkit/core": "18.2.19", "rxjs": "7.8.1" }, "engines": { @@ -1452,9 +1479,9 @@ } }, "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.12.tgz", - "integrity": "sha512-NtB6ypsaDyPE6/fqWOdfTmACs+yK5RqfH5tStEzWFeeDsIEDYKsJ06ypuRep7qTjYus5Rmttk0Ds+cFgz8JdUQ==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1581,13 +1608,13 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.14.tgz", - "integrity": "sha512-mukjZIHHB7gWratq8fZwUq5WZ+1bF4feG/idXr1wgQ+/FqWjs2PP7HDesHVcPymmRulpTyCpB7TNB1O1fgnCpA==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.19.tgz", + "integrity": "sha512-P/0KjkzOf2ZShuShx3cBbjLI7XlcS6B/yCRBo1MQfCC4cZfmzPQoUEOSQeYZgy5pnC24f+dKh/+TWc5uYL/Lvg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.14", + "@angular-devkit/core": "18.2.19", "jsonc-parser": "3.3.1", "magic-string": "0.30.11", "ora": "5.4.1", @@ -1600,9 +1627,9 @@ } }, "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.14.tgz", - "integrity": "sha512-UGIGOjXuOyCW+5S4tINu7e6LOu738CmTw3h7Ui1I8OzdTIYJcYJrei8sgrwDwOYADRal+p0MeMlnykH3TM5XBA==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2034,14 +2061,14 @@ } }, "node_modules/@angular/build": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.12.tgz", - "integrity": "sha512-4Ohz+OSILoL+cCAQ4UTiCT5v6pctu3fXNoNpTEUK46OmxELk9jDITO5rNyNS7TxBn9wY69kjX5VcDf7MenquFQ==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.19.tgz", + "integrity": "sha512-dTqR+mhcZWtCRyOafvzHNVpYxMQnt8HHHqNM0kyEMzcztXL2L9zDlKr0H9d+AgGGq/v4qwCh+1gFDxsHByZwMQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.12", + "@angular-devkit/architect": "0.1802.19", "@babel/core": "7.25.2", "@babel/helper-annotate-as-pure": "7.24.7", "@babel/helper-split-export-declaration": "7.24.7", @@ -2063,7 +2090,7 @@ "rollup": "4.22.4", "sass": "1.77.6", "semver": "7.6.3", - "vite": "5.4.6", + "vite": "~5.4.17", "watchpack": "2.4.1" }, "engines": { @@ -2103,13 +2130,13 @@ } }, "node_modules/@angular/build/node_modules/@angular-devkit/architect": { - "version": "0.1802.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.12.tgz", - "integrity": "sha512-bepVb2/GtJppYKaeW8yTGE6egmoWZ7zagFDsmBdbF+BYp+HmeoPsclARcdryBPVq68zedyTRdvhWSUTbw1AYuw==", + "version": "0.1802.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz", + "integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.12", + "@angular-devkit/core": "18.2.19", "rxjs": "7.8.1" }, "engines": { @@ -2119,9 +2146,9 @@ } }, "node_modules/@angular/build/node_modules/@angular-devkit/core": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.12.tgz", - "integrity": "sha512-NtB6ypsaDyPE6/fqWOdfTmACs+yK5RqfH5tStEzWFeeDsIEDYKsJ06ypuRep7qTjYus5Rmttk0Ds+cFgz8JdUQ==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2325,18 +2352,18 @@ } }, "node_modules/@angular/cli": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.12.tgz", - "integrity": "sha512-xhuZ/b7IhqNw1MgXf+arWf4x+GfUSt/IwbdWU4+CO8A7h0Y46zQywouP/KUK3cMQZfVdHdciTBvlpF3vFacA6Q==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.19.tgz", + "integrity": "sha512-LGVMTc36JQuw8QX8Sclxyei306EQW3KslopXbf7cfqt6D5/fHS+FqqA0O7V8ob/vOGMca+l6hQD27nW5Y3W6pA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1802.12", - "@angular-devkit/core": "18.2.12", - "@angular-devkit/schematics": "18.2.12", + "@angular-devkit/architect": "0.1802.19", + "@angular-devkit/core": "18.2.19", + "@angular-devkit/schematics": "18.2.19", "@inquirer/prompts": "5.3.8", "@listr2/prompt-adapter-inquirer": "2.0.15", - "@schematics/angular": "18.2.12", + "@schematics/angular": "18.2.19", "@yarnpkg/lockfile": "1.1.0", "ini": "4.1.3", "jsonc-parser": "3.3.1", @@ -2359,13 +2386,13 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { - "version": "0.1802.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.12.tgz", - "integrity": "sha512-bepVb2/GtJppYKaeW8yTGE6egmoWZ7zagFDsmBdbF+BYp+HmeoPsclARcdryBPVq68zedyTRdvhWSUTbw1AYuw==", + "version": "0.1802.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz", + "integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.12", + "@angular-devkit/core": "18.2.19", "rxjs": "7.8.1" }, "engines": { @@ -2375,9 +2402,9 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.12.tgz", - "integrity": "sha512-NtB6ypsaDyPE6/fqWOdfTmACs+yK5RqfH5tStEzWFeeDsIEDYKsJ06ypuRep7qTjYus5Rmttk0Ds+cFgz8JdUQ==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2402,25 +2429,6 @@ } } }, - "node_modules/@angular/cli/node_modules/@angular-devkit/schematics": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.12.tgz", - "integrity": "sha512-mMea9txHbnCX5lXLHlo0RAgfhFHDio45/jMsREM2PA8UtVf2S8ltXz7ZwUrUyMQRv8vaSfn4ijDstF4hDMnRgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "18.2.12", - "jsonc-parser": "3.3.1", - "magic-string": "0.30.11", - "ora": "5.4.1", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, "node_modules/@angular/cli/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -2718,14 +2726,14 @@ "license": "ISC" }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -2786,15 +2794,16 @@ } }, "node_modules/@babel/generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", - "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz", + "integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==", "license": "MIT", "dependencies": { - "@babel/types": "^7.25.0", + "@babel/parser": "^7.26.10", + "@babel/types": "^7.26.10", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" @@ -2916,13 +2925,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", - "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-annotate-as-pure": "^7.27.1", "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, @@ -2934,13 +2943,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", + "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3031,9 +3040,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3116,18 +3125,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3158,25 +3167,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz", - "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", "license": "MIT", "dependencies": { - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", - "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.9" + "@babel/types": "^7.27.1" }, "bin": { "parser": "bin/babel-parser.js" @@ -3616,16 +3625,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", - "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", + "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-remap-async-to-generator": "^7.25.0", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/traverse": "^7.25.0" + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.26.8" }, "engines": { "node": ">=6.9.0" @@ -3635,15 +3643,15 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", - "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-remap-async-to-generator": "^7.24.7" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -4281,6 +4289,23 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-reserved-words": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", @@ -4298,16 +4323,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", - "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz", + "integrity": "sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-corejs3": "^0.11.0", "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, @@ -4318,6 +4343,20 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-runtime/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -4598,9 +4637,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", - "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -4610,30 +4649,30 @@ } }, "node_modules/@babel/template": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", - "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.9", - "@babel/parser": "^7.26.9", - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.9", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -4642,13 +4681,13 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", - "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9", + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -4657,26 +4696,14 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@babel/types": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", - "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -5022,23 +5049,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@compodoc/compodoc/node_modules/@babel/generator": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", - "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@compodoc/compodoc/node_modules/@babel/plugin-syntax-import-attributes": { "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", @@ -5055,42 +5065,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@compodoc/compodoc/node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", - "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-remap-async-to-generator": "^7.25.9", - "@babel/traverse": "^7.26.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@compodoc/compodoc/node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", - "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@compodoc/compodoc/node_modules/@babel/plugin-transform-private-methods": { "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.7.tgz", @@ -5233,19 +5207,6 @@ "node": ">= 6" } }, - "node_modules/@compodoc/compodoc/node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@compodoc/compodoc/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -8353,9 +8314,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.12.tgz", - "integrity": "sha512-FFJAwtWbtpncMOVNuULPBwFJB7GSjiUwO93eGTzRp8O4EPQ8lCQeFbezQm/NP34+T0+GBLGzPSuQT+muob8YKw==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.19.tgz", + "integrity": "sha512-bExj5JrByKPibsqBbn5Pjn8lo91AUOTsyP2hgKpnOnmSr62rhWSiRwXltgz2MCiZRmuUznpt93WiOLixgYfYvQ==", "dev": true, "license": "MIT", "engines": { @@ -9633,14 +9594,14 @@ "license": "MIT" }, "node_modules/@schematics/angular": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.12.tgz", - "integrity": "sha512-sIoeipsisK5eTLW3XuNZYcal83AfslBbgI7LnV+3VrXwpasKPGHwo2ZdwhCd2IXAkuJ02Iyu7MyV0aQRM9i/3g==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.19.tgz", + "integrity": "sha512-s9aynH/fwB/LT94miVfsaL2C4Qd5BLgjMzWFx7iJ8Hyv7FjOBGYO6eGVovjCt2c6/abG+GQAk4EBOCfg3AUtCA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.12", - "@angular-devkit/schematics": "18.2.12", + "@angular-devkit/core": "18.2.19", + "@angular-devkit/schematics": "18.2.19", "jsonc-parser": "3.3.1" }, "engines": { @@ -9650,9 +9611,9 @@ } }, "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.12.tgz", - "integrity": "sha512-NtB6ypsaDyPE6/fqWOdfTmACs+yK5RqfH5tStEzWFeeDsIEDYKsJ06ypuRep7qTjYus5Rmttk0Ds+cFgz8JdUQ==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9677,25 +9638,6 @@ } } }, - "node_modules/@schematics/angular/node_modules/@angular-devkit/schematics": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.12.tgz", - "integrity": "sha512-mMea9txHbnCX5lXLHlo0RAgfhFHDio45/jMsREM2PA8UtVf2S8ltXz7ZwUrUyMQRv8vaSfn4ijDstF4hDMnRgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "18.2.12", - "jsonc-parser": "3.3.1", - "magic-string": "0.30.11", - "ora": "5.4.1", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, "node_modules/@schematics/angular/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -21441,9 +21383,9 @@ } }, "node_modules/http-proxy-middleware": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz", - "integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", + "integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==", "dev": true, "license": "MIT", "dependencies": { @@ -24398,15 +24340,15 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -35658,9 +35600,9 @@ } }, "node_modules/vite": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", - "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 6fe463ac387..81f1c92d5a5 100644 --- a/package.json +++ b/package.json @@ -36,9 +36,9 @@ "libs/**/*" ], "devDependencies": { - "@angular-devkit/build-angular": "18.2.12", + "@angular-devkit/build-angular": "18.2.19", "@angular-eslint/schematics": "18.4.3", - "@angular/cli": "18.2.12", + "@angular/cli": "18.2.19", "@angular/compiler-cli": "18.2.13", "@babel/core": "7.24.9", "@babel/preset-env": "7.24.8", @@ -46,7 +46,7 @@ "@electron/notarize": "2.5.0", "@electron/rebuild": "3.7.2", "@lit-labs/signals": "0.1.2", - "@ngtools/webpack": "18.2.12", + "@ngtools/webpack": "18.2.19", "@storybook/addon-a11y": "8.6.12", "@storybook/addon-actions": "8.6.12", "@storybook/addon-designs": "8.2.1", From e538425e7a5ce635b1fbe88835a4f392d31413ee Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Fri, 9 May 2025 12:07:14 -0700 Subject: [PATCH 003/245] [PM-21343] - add download bitwarden to list of nudges for settings badge (#14682) * add download bitwarden to list of nudges for settings badge * update tests --- .../src/services/vault-nudges.service.spec.ts | 33 ++++++++++++++----- .../src/services/vault-nudges.service.ts | 2 +- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/libs/vault/src/services/vault-nudges.service.spec.ts b/libs/vault/src/services/vault-nudges.service.spec.ts index 89465fc5382..40c58644309 100644 --- a/libs/vault/src/services/vault-nudges.service.spec.ts +++ b/libs/vault/src/services/vault-nudges.service.spec.ts @@ -12,7 +12,11 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { FakeStateProvider, mockAccountServiceWith } from "../../../common/spec"; -import { HasItemsNudgeService, EmptyVaultNudgeService } from "./custom-nudges-services"; +import { + HasItemsNudgeService, + EmptyVaultNudgeService, + DownloadBitwardenNudgeService, +} from "./custom-nudges-services"; import { DefaultSingleNudgeService } from "./default-single-nudge.service"; import { VaultNudgesService, VaultNudgeType } from "./vault-nudges.service"; @@ -25,6 +29,8 @@ describe("Vault Nudges Service", () => { getFeatureFlag: jest.fn().mockReturnValue(true), }; + const vaultNudgeServices = [EmptyVaultNudgeService, DownloadBitwardenNudgeService]; + beforeEach(async () => { fakeStateProvider = new FakeStateProvider(mockAccountServiceWith("user-id" as UserId)); @@ -46,6 +52,10 @@ describe("Vault Nudges Service", () => { provide: HasItemsNudgeService, useValue: mock(), }, + { + provide: DownloadBitwardenNudgeService, + useValue: mock(), + }, { provide: EmptyVaultNudgeService, useValue: mock(), @@ -130,11 +140,14 @@ describe("Vault Nudges Service", () => { describe("HasActiveBadges", () => { it("should return true if a nudgeType with hasBadgeDismissed === false", async () => { - TestBed.overrideProvider(EmptyVaultNudgeService, { - useValue: { - nudgeStatus$: () => of({ hasBadgeDismissed: false, hasSpotlightDismissed: false }), - }, + vaultNudgeServices.forEach((service) => { + TestBed.overrideProvider(service, { + useValue: { + nudgeStatus$: () => of({ hasBadgeDismissed: false, hasSpotlightDismissed: false }), + }, + }); }); + const service = testBed.inject(VaultNudgesService); const result = await firstValueFrom(service.hasActiveBadges$("user-id" as UserId)); @@ -142,10 +155,12 @@ describe("Vault Nudges Service", () => { expect(result).toBe(true); }); it("should return false if all nudgeTypes have hasBadgeDismissed === true", async () => { - TestBed.overrideProvider(EmptyVaultNudgeService, { - useValue: { - nudgeStatus$: () => of({ hasBadgeDismissed: true, hasSpotlightDismissed: true }), - }, + vaultNudgeServices.forEach((service) => { + TestBed.overrideProvider(service, { + useValue: { + nudgeStatus$: () => of({ hasBadgeDismissed: true, hasSpotlightDismissed: false }), + }, + }); }); const service = testBed.inject(VaultNudgesService); diff --git a/libs/vault/src/services/vault-nudges.service.ts b/libs/vault/src/services/vault-nudges.service.ts index e04cb609d7f..be6e978eaa0 100644 --- a/libs/vault/src/services/vault-nudges.service.ts +++ b/libs/vault/src/services/vault-nudges.service.ts @@ -114,7 +114,7 @@ export class VaultNudgesService { */ hasActiveBadges$(userId: UserId): Observable { // Add more nudge types here if they have the settings badge feature - const nudgeTypes = [VaultNudgeType.EmptyVaultNudge]; + const nudgeTypes = [VaultNudgeType.EmptyVaultNudge, VaultNudgeType.DownloadBitwarden]; const nudgeTypesWithBadge$ = nudgeTypes.map((nudge) => { return this.getNudgeService(nudge) From 6b6f9577f4344daa32767824e9d209e640b5f72e Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Fri, 9 May 2025 12:08:12 -0700 Subject: [PATCH 004/245] multiple fixes for bitwarden component (#14685) --- apps/browser/src/_locales/en/messages.json | 6 ++++ .../download-bitwarden.component.html | 36 +++++++++++++------ .../settings/download-bitwarden.component.ts | 3 +- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 1c3214a6ef7..57a0e78fd1b 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -4551,6 +4551,12 @@ "downloadFromBitwardenNow": { "message": "Download from bitwarden.com now" }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, diff --git a/apps/browser/src/vault/popup/settings/download-bitwarden.component.html b/apps/browser/src/vault/popup/settings/download-bitwarden.component.html index ad063691e76..20896a3c782 100644 --- a/apps/browser/src/vault/popup/settings/download-bitwarden.component.html +++ b/apps/browser/src/vault/popup/settings/download-bitwarden.component.html @@ -2,7 +2,6 @@ -

@@ -20,16 +19,30 @@ /> @@ -41,6 +54,7 @@ {{ "downloadFromBitwardenNow" | i18n }} diff --git a/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts b/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts index b51619b86d1..9f04bb58c34 100644 --- a/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts +++ b/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts @@ -6,7 +6,7 @@ import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { CardComponent, TypographyModule } from "@bitwarden/components"; +import { CardComponent, LinkModule, TypographyModule } from "@bitwarden/components"; import { VaultNudgesService, VaultNudgeType } from "@bitwarden/vault"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; @@ -27,6 +27,7 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co CardComponent, TypographyModule, CurrentAccountComponent, + LinkModule, ], }) export class DownloadBitwardenComponent implements OnInit { From 51e327e20bdd44f83af4991b8246ebfc12ed6718 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 9 May 2025 21:39:38 +0200 Subject: [PATCH 005/245] Clean up stretchKey (#14520) --- .../services/key-generation.service.spec.ts | 20 +++++++++++++++++++ .../services/key-generation.service.ts | 10 ++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/libs/common/src/platform/services/key-generation.service.spec.ts b/libs/common/src/platform/services/key-generation.service.spec.ts index f75eaeb25be..0a9e997b428 100644 --- a/libs/common/src/platform/services/key-generation.service.spec.ts +++ b/libs/common/src/platform/services/key-generation.service.spec.ts @@ -5,6 +5,7 @@ import { PBKDF2KdfConfig, Argon2KdfConfig } from "@bitwarden/key-management"; import { CryptoFunctionService } from "../../key-management/crypto/abstractions/crypto-function.service"; import { CsprngArray } from "../../types/csprng"; import { EncryptionType } from "../enums"; +import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; import { KeyGenerationService } from "./key-generation.service"; @@ -98,4 +99,23 @@ describe("KeyGenerationService", () => { expect(key.inner().type).toEqual(EncryptionType.AesCbc256_B64); }); }); + + describe("stretchKey", () => { + it("should stretch a key", async () => { + const key = new SymmetricCryptoKey(new Uint8Array(32)); + + cryptoFunctionService.hkdf.mockResolvedValue(new Uint8Array(64)); + + const stretchedKey = await sut.stretchKey(key); + + expect(stretchedKey.inner().type).toEqual(EncryptionType.AesCbc256_HmacSha256_B64); + }); + it("should throw if key is not 32 bytes", async () => { + const key = new SymmetricCryptoKey(new Uint8Array(64)); + + await expect(sut.stretchKey(key)).rejects.toThrow( + "Key passed into stretchKey is not a 256-bit key.", + ); + }); + }); }); diff --git a/libs/common/src/platform/services/key-generation.service.ts b/libs/common/src/platform/services/key-generation.service.ts index 8f9e6856aa0..dcd1f4f95d7 100644 --- a/libs/common/src/platform/services/key-generation.service.ts +++ b/libs/common/src/platform/services/key-generation.service.ts @@ -1,11 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { MasterKey, PinKey } from "@bitwarden/common/types/key"; import { KdfConfig, PBKDF2KdfConfig, Argon2KdfConfig, KdfType } from "@bitwarden/key-management"; import { CryptoFunctionService } from "../../key-management/crypto/abstractions/crypto-function.service"; import { CsprngArray } from "../../types/csprng"; import { KeyGenerationService as KeyGenerationServiceAbstraction } from "../abstractions/key-generation.service"; +import { EncryptionType } from "../enums"; import { Utils } from "../misc/utils"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; @@ -79,7 +79,13 @@ export class KeyGenerationService implements KeyGenerationServiceAbstraction { return new SymmetricCryptoKey(key); } - async stretchKey(key: MasterKey | PinKey): Promise { + async stretchKey(key: SymmetricCryptoKey): Promise { + // The key to be stretched is actually usually the output of a KDF, and not actually meant for AesCbc256_B64 encryption, + // but has the same key length. Only 256-bit key materials should be stretched. + if (key.inner().type != EncryptionType.AesCbc256_B64) { + throw new Error("Key passed into stretchKey is not a 256-bit key."); + } + const newKey = new Uint8Array(64); // Master key and pin key are always 32 bytes const encKey = await this.cryptoFunctionService.hkdfExpand( From 83dc4706f3605d9dd7cb7c4aa5e529f858d006cf Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Fri, 9 May 2025 16:21:47 -0400 Subject: [PATCH 006/245] [PM-21279] Cipher for url speedup (#14684) * Use `perUserCache$` for `getAllDecryptedForUrl` * Fix loads before first lock * Filter out null ciphers --- libs/common/src/vault/services/cipher.service.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index a32568c8112..169568d44e9 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -519,8 +519,15 @@ export class CipherService implements CipherServiceAbstraction { includeOtherTypes?: CipherType[], defaultMatch: UriMatchStrategySetting = null, ): Promise { - const ciphers = await this.getAllDecrypted(userId); - return await this.filterCiphersForUrl(ciphers, url, includeOtherTypes, defaultMatch); + return await firstValueFrom( + this.cipherViews$(userId).pipe( + filter((c) => c != null), + switchMap( + async (ciphers) => + await this.filterCiphersForUrl(ciphers, url, includeOtherTypes, defaultMatch), + ), + ), + ); } async filterCiphersForUrl( From 5470d6a6d97c9f0e94b901e41ded66bb5ca49168 Mon Sep 17 00:00:00 2001 From: Miles Blackwood Date: Fri, 9 May 2025 17:30:32 -0400 Subject: [PATCH 007/245] Fix i18n key mis-entry. (#14711) --- apps/browser/src/autofill/notification/bar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 4b9ba58d586..3fd6016eb33 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -61,7 +61,7 @@ function getI18n() { loginSaveSuccess: chrome.i18n.getMessage("loginSaveSuccess"), loginSaveConfirmation: chrome.i18n.getMessage("loginSaveConfirmation"), loginUpdateSuccess: chrome.i18n.getMessage("loginUpdateSuccess"), - loginUpdateConfirmation: chrome.i18n.getMessage("loginUpdatedConfirmation"), + loginUpdatedConfirmation: chrome.i18n.getMessage("loginUpdatedConfirmation"), loginUpdateTaskSuccess: chrome.i18n.getMessage("loginUpdateTaskSuccess"), loginUpdateTaskSuccessAdditional: chrome.i18n.getMessage("loginUpdateTaskSuccessAdditional"), nextSecurityTaskAction: chrome.i18n.getMessage("nextSecurityTaskAction"), From 1b756df749a23b33f6f69b9740a8c3ce6a6fb5fb Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Fri, 9 May 2025 20:21:25 -0400 Subject: [PATCH 008/245] [PM-14484] `ApiService` showing html in error message (#14658) * Make ApiService more testable * Add ApiService tests * Switch to only reading text/plain content --- .../browser/src/background/main.background.ts | 1 + .../src/platform/services/node-api.service.ts | 1 + libs/angular/src/services/injection-tokens.ts | 3 + .../src/services/jslib-services.module.ts | 6 + libs/common/src/services/api.service.spec.ts | 203 ++++++++++++++++++ libs/common/src/services/api.service.ts | 27 ++- 6 files changed, 230 insertions(+), 11 deletions(-) create mode 100644 libs/common/src/services/api.service.spec.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index d295fddda52..85a9cd27c57 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -720,6 +720,7 @@ export default class MainBackground { this.logService, (logoutReason: LogoutReason, userId?: UserId) => this.logout(logoutReason, userId), this.vaultTimeoutSettingsService, + { createRequest: (url, request) => new Request(url, request) }, ); this.fileUploadService = new FileUploadService(this.logService, this.apiService); diff --git a/apps/cli/src/platform/services/node-api.service.ts b/apps/cli/src/platform/services/node-api.service.ts index 8c7629fb3d9..d695272364b 100644 --- a/apps/cli/src/platform/services/node-api.service.ts +++ b/apps/cli/src/platform/services/node-api.service.ts @@ -39,6 +39,7 @@ export class NodeApiService extends ApiService { logService, logoutCallback, vaultTimeoutSettingsService, + { createRequest: (url, request) => new Request(url, request) }, customUserAgent, ); } diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index a63d862b0d8..d82ff021962 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -13,6 +13,7 @@ import { import { Theme } from "@bitwarden/common/platform/enums"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { Message } from "@bitwarden/common/platform/messaging"; +import { HttpOperations } from "@bitwarden/common/services/api.service"; import { SafeInjectionToken } from "@bitwarden/ui-common"; // Re-export the SafeInjectionToken from ui-common export { SafeInjectionToken } from "@bitwarden/ui-common"; @@ -61,3 +62,5 @@ export const REFRESH_ACCESS_TOKEN_ERROR_CALLBACK = new SafeInjectionToken<() => export const ENV_ADDITIONAL_REGIONS = new SafeInjectionToken( "ENV_ADDITIONAL_REGIONS", ); + +export const HTTP_OPERATIONS = new SafeInjectionToken("HTTP_OPERATIONS"); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index d82df8574ff..4e7c558a0f0 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -337,6 +337,7 @@ import { CLIENT_TYPE, DEFAULT_VAULT_TIMEOUT, ENV_ADDITIONAL_REGIONS, + HTTP_OPERATIONS, INTRAPROCESS_MESSAGING_SUBJECT, LOCALES_DIRECTORY, LOCKED_CALLBACK, @@ -700,6 +701,10 @@ const safeProviders: SafeProvider[] = [ }, deps: [ToastService, I18nServiceAbstraction], }), + safeProvider({ + provide: HTTP_OPERATIONS, + useValue: { createRequest: (url, request) => new Request(url, request) }, + }), safeProvider({ provide: ApiServiceAbstraction, useClass: ApiService, @@ -712,6 +717,7 @@ const safeProviders: SafeProvider[] = [ LogService, LOGOUT_CALLBACK, VaultTimeoutSettingsService, + HTTP_OPERATIONS, ], }), safeProvider({ diff --git a/libs/common/src/services/api.service.spec.ts b/libs/common/src/services/api.service.spec.ts new file mode 100644 index 00000000000..eca6066b9b7 --- /dev/null +++ b/libs/common/src/services/api.service.spec.ts @@ -0,0 +1,203 @@ +import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { LogoutReason } from "@bitwarden/auth/common"; + +import { TokenService } from "../auth/abstractions/token.service"; +import { DeviceType } from "../enums"; +import { VaultTimeoutSettingsService } from "../key-management/vault-timeout"; +import { ErrorResponse } from "../models/response/error.response"; +import { AppIdService } from "../platform/abstractions/app-id.service"; +import { Environment, EnvironmentService } from "../platform/abstractions/environment.service"; +import { LogService } from "../platform/abstractions/log.service"; +import { PlatformUtilsService } from "../platform/abstractions/platform-utils.service"; + +import { ApiService, HttpOperations } from "./api.service"; + +describe("ApiService", () => { + let tokenService: MockProxy; + let platformUtilsService: MockProxy; + let environmentService: MockProxy; + let appIdService: MockProxy; + let refreshAccessTokenErrorCallback: jest.Mock; + let logService: MockProxy; + let logoutCallback: jest.Mock, [reason: LogoutReason]>; + let vaultTimeoutSettingsService: MockProxy; + let httpOperations: MockProxy; + + let sut: ApiService; + + beforeEach(() => { + tokenService = mock(); + platformUtilsService = mock(); + platformUtilsService.getDevice.mockReturnValue(DeviceType.ChromeExtension); + + environmentService = mock(); + appIdService = mock(); + refreshAccessTokenErrorCallback = jest.fn(); + logService = mock(); + logoutCallback = jest.fn(); + vaultTimeoutSettingsService = mock(); + httpOperations = mock(); + + sut = new ApiService( + tokenService, + platformUtilsService, + environmentService, + appIdService, + refreshAccessTokenErrorCallback, + logService, + logoutCallback, + vaultTimeoutSettingsService, + httpOperations, + "custom-user-agent", + ); + }); + + describe("send", () => { + it("handles ok GET", async () => { + environmentService.environment$ = of({ + getApiUrl: () => "https://example.com", + } satisfies Partial as Environment); + + httpOperations.createRequest.mockImplementation((url, request) => { + return { + url: url, + cache: request.cache, + credentials: request.credentials, + method: request.method, + mode: request.mode, + signal: request.signal, + headers: new Headers(request.headers), + } satisfies Partial as unknown as Request; + }); + + tokenService.getAccessToken.mockResolvedValue("access_token"); + tokenService.tokenNeedsRefresh.mockResolvedValue(false); + + const nativeFetch = jest.fn, [request: Request]>(); + + nativeFetch.mockImplementation((request) => { + return Promise.resolve({ + ok: true, + status: 200, + json: () => Promise.resolve({ hello: "world" }), + headers: new Headers({ + "content-type": "application/json", + }), + } satisfies Partial as Response); + }); + + sut.nativeFetch = nativeFetch; + + const response = await sut.send("GET", "/something", null, true, true, null, null); + + expect(nativeFetch).toHaveBeenCalledTimes(1); + const request = nativeFetch.mock.calls[0][0]; + // This should get set for users of send + expect(request.cache).toBe("no-store"); + // TODO: Could expect on the credentials parameter + expect(request.headers.get("Device-Type")).toBe("2"); // Chrome Extension + // Custom user agent should get set + expect(request.headers.get("User-Agent")).toBe("custom-user-agent"); + // This should be set when the caller has indicated there is a response + expect(request.headers.get("Accept")).toBe("application/json"); + // If they have indicated that it's authed, then the authorization header should get set. + expect(request.headers.get("Authorization")).toBe("Bearer access_token"); + // The response body + expect(response).toEqual({ hello: "world" }); + }); + }); + + const errorData: { + name: string; + input: Partial; + error: Partial; + }[] = [ + { + name: "json response in camel case", + input: { + json: () => Promise.resolve({ message: "Something bad happened." }), + headers: new Headers({ + "content-type": "application/json", + }), + }, + error: { + message: "Something bad happened.", + }, + }, + { + name: "json response in pascal case", + input: { + json: () => Promise.resolve({ Message: "Something bad happened." }), + headers: new Headers({ + "content-type": "application/json", + }), + }, + error: { + message: "Something bad happened.", + }, + }, + { + name: "json response with charset in content type", + input: { + json: () => Promise.resolve({ message: "Something bad happened." }), + headers: new Headers({ + "content-type": "application/json; charset=utf-8", + }), + }, + error: { + message: "Something bad happened.", + }, + }, + { + name: "text/plain response", + input: { + text: () => Promise.resolve("Something bad happened."), + headers: new Headers({ + "content-type": "text/plain", + }), + }, + error: { + message: "Something bad happened.", + }, + }, + ]; + + it.each(errorData)( + "throws error-like response when not ok response with $name", + async ({ input, error }) => { + environmentService.environment$ = of({ + getApiUrl: () => "https://example.com", + } satisfies Partial as Environment); + + httpOperations.createRequest.mockImplementation((url, request) => { + return { + url: url, + cache: request.cache, + credentials: request.credentials, + method: request.method, + mode: request.mode, + signal: request.signal, + headers: new Headers(request.headers), + } satisfies Partial as unknown as Request; + }); + + const nativeFetch = jest.fn, [request: Request]>(); + + nativeFetch.mockImplementation((request) => { + return Promise.resolve({ + ok: false, + status: 400, + ...input, + } satisfies Partial as Response); + }); + + sut.nativeFetch = nativeFetch; + + await expect( + async () => await sut.send("GET", "/something", null, true, true, null, null), + ).rejects.toMatchObject(error); + }, + ); +}); diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index fb4d08db81c..5c4bcdedb26 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -139,6 +139,10 @@ import { AttachmentResponse } from "../vault/models/response/attachment.response import { CipherResponse } from "../vault/models/response/cipher.response"; import { OptionalCipherResponse } from "../vault/models/response/optional-cipher.response"; +export type HttpOperations = { + createRequest: (url: string, request: RequestInit) => Request; +}; + /** * @deprecated The `ApiService` class is deprecated and calls should be extracted into individual * api services. The `send` method is still allowed to be used within api services. For background @@ -166,6 +170,7 @@ export class ApiService implements ApiServiceAbstraction { private logService: LogService, private logoutCallback: (logoutReason: LogoutReason) => Promise, private vaultTimeoutSettingsService: VaultTimeoutSettingsService, + private readonly httpOperations: HttpOperations, private customUserAgent: string = null, ) { this.device = platformUtilsService.getDevice(); @@ -217,7 +222,7 @@ export class ApiService implements ApiServiceAbstraction { const env = await firstValueFrom(this.environmentService.environment$); const response = await this.fetch( - new Request(env.getIdentityUrl() + "/connect/token", { + this.httpOperations.createRequest(env.getIdentityUrl() + "/connect/token", { body: this.qsStringify(identityToken), credentials: await this.getCredentials(), cache: "no-store", @@ -1409,7 +1414,7 @@ export class ApiService implements ApiServiceAbstraction { } const env = await firstValueFrom(this.environmentService.environment$); const response = await this.fetch( - new Request(env.getEventsUrl() + "/collect", { + this.httpOperations.createRequest(env.getEventsUrl() + "/collect", { cache: "no-store", credentials: await this.getCredentials(), method: "POST", @@ -1456,7 +1461,7 @@ export class ApiService implements ApiServiceAbstraction { const authHeader = await this.getActiveBearerToken(); const response = await this.fetch( - new Request(keyConnectorUrl + "/user-keys", { + this.httpOperations.createRequest(keyConnectorUrl + "/user-keys", { cache: "no-store", method: "GET", headers: new Headers({ @@ -1481,7 +1486,7 @@ export class ApiService implements ApiServiceAbstraction { const authHeader = await this.getActiveBearerToken(); const response = await this.fetch( - new Request(keyConnectorUrl + "/user-keys", { + this.httpOperations.createRequest(keyConnectorUrl + "/user-keys", { cache: "no-store", method: "POST", headers: new Headers({ @@ -1501,7 +1506,7 @@ export class ApiService implements ApiServiceAbstraction { async getKeyConnectorAlive(keyConnectorUrl: string) { const response = await this.fetch( - new Request(keyConnectorUrl + "/alive", { + this.httpOperations.createRequest(keyConnectorUrl + "/alive", { cache: "no-store", method: "GET", headers: new Headers({ @@ -1570,7 +1575,7 @@ export class ApiService implements ApiServiceAbstraction { const env = await firstValueFrom(this.environmentService.environment$); const path = `/sso/prevalidate?domainHint=${encodeURIComponent(identifier)}`; const response = await this.fetch( - new Request(env.getIdentityUrl() + path, { + this.httpOperations.createRequest(env.getIdentityUrl() + path, { cache: "no-store", credentials: await this.getCredentials(), headers: headers, @@ -1711,7 +1716,7 @@ export class ApiService implements ApiServiceAbstraction { const env = await firstValueFrom(this.environmentService.environment$); const decodedToken = await this.tokenService.decodeAccessToken(); const response = await this.fetch( - new Request(env.getIdentityUrl() + "/connect/token", { + this.httpOperations.createRequest(env.getIdentityUrl() + "/connect/token", { body: this.qsStringify({ grant_type: "refresh_token", client_id: decodedToken.client_id, @@ -1820,7 +1825,7 @@ export class ApiService implements ApiServiceAbstraction { }; requestInit.headers = requestHeaders; requestInit.body = requestBody; - const response = await this.fetch(new Request(requestUrl, requestInit)); + const response = await this.fetch(this.httpOperations.createRequest(requestUrl, requestInit)); const responseType = response.headers.get("content-type"); const responseIsJson = responseType != null && responseType.indexOf("application/json") !== -1; @@ -1889,7 +1894,7 @@ export class ApiService implements ApiServiceAbstraction { let responseJson: any = null; if (this.isJsonResponse(response)) { responseJson = await response.json(); - } else if (this.isTextResponse(response)) { + } else if (this.isTextPlainResponse(response)) { responseJson = { Message: await response.text() }; } @@ -1945,8 +1950,8 @@ export class ApiService implements ApiServiceAbstraction { return typeHeader != null && typeHeader.indexOf("application/json") > -1; } - private isTextResponse(response: Response): boolean { + private isTextPlainResponse(response: Response): boolean { const typeHeader = response.headers.get("content-type"); - return typeHeader != null && typeHeader.indexOf("text") > -1; + return typeHeader != null && typeHeader.indexOf("text/plain") > -1; } } From 23d4f04b22d52a690de0f6e1ba0918bbcf142c0d Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Fri, 9 May 2025 17:51:34 -0700 Subject: [PATCH 009/245] set cipher form to partial edit for non edit permission (#14639) --- apps/desktop/src/vault/app/vault/vault-v2.component.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index 4da2db101ed..66e77580d1c 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -514,6 +514,9 @@ export class VaultV2Component implements OnInit, OnDestroy { this.cipherId = cipher.id; this.cipher = cipher; await this.buildFormConfig("edit"); + if (!cipher.edit && this.config) { + this.config.mode = "partial-edit"; + } this.action = "edit"; await this.go().catch(() => {}); } From 2282a74abda260dd21916c3e330409c2a3e181e8 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Fri, 9 May 2025 18:09:26 -0700 Subject: [PATCH 010/245] [PM-21021] - fix toggled hidden field state for custom fields view (#14595) * fix toggled hidden field state for custom fields view * fix hidden field toggle for cards and login details --- .../card-details-view.component.html | 2 ++ .../card-details-view.component.ts | 19 +++++++++++++++++-- .../custom-fields-v2.component.ts | 10 ++++++++-- .../login-credentials-view.component.html | 1 + .../login-credentials-view.component.ts | 19 +++++++++++++++++-- 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/libs/vault/src/cipher-view/card-details/card-details-view.component.html b/libs/vault/src/cipher-view/card-details/card-details-view.component.html index fff771b6465..ff61addd7db 100644 --- a/libs/vault/src/cipher-view/card-details/card-details-view.component.html +++ b/libs/vault/src/cipher-view/card-details/card-details-view.component.html @@ -33,6 +33,7 @@ bitIconButton bitPasswordInputToggle data-testid="toggle-number" + [toggled]="revealCardNumber" (toggledChange)="logCardEvent($event, EventType.Cipher_ClientToggledCardNumberVisible)" > diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts index 27d81f32ee6..d99ac438f27 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts @@ -1,7 +1,15 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { CommonModule, DatePipe } from "@angular/common"; -import { Component, EventEmitter, inject, Input, Output } from "@angular/core"; +import { + Component, + EventEmitter, + inject, + Input, + OnChanges, + Output, + SimpleChanges, +} from "@angular/core"; import { Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -51,7 +59,7 @@ type TotpCodeValues = { LinkModule, ], }) -export class LoginCredentialsViewComponent { +export class LoginCredentialsViewComponent implements OnChanges { @Input() cipher: CipherView; @Input() activeUserId: UserId; @Input() hadPendingChangePasswordTask: boolean; @@ -85,6 +93,13 @@ export class LoginCredentialsViewComponent { return `${dateCreated} ${creationDate}`; } + ngOnChanges(changes: SimpleChanges): void { + if (changes["cipher"]) { + this.passwordRevealed = false; + this.showPasswordCount = false; + } + } + async getPremium(organizationId?: string) { await this.premiumUpgradeService.promptForPremium(organizationId); } From 5408a62b7df4631564601fb5fc056e718b6c74f9 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 12 May 2025 11:41:45 +0200 Subject: [PATCH 011/245] [PM-21001] Move KM usage of encrypt service (#14541) * Add new encrypt service functions * Undo changes * Cleanup * Fix build * Fix comments * Move KM usage of encrypt service * Fix build --- .../background/nativeMessaging.background.ts | 5 +-- .../biometric-message-handler.service.spec.ts | 8 ++-- .../biometric-message-handler.service.ts | 4 +- .../crypto/abstractions/encrypt.service.ts | 1 + .../device-trust.service.implementation.ts | 11 +++-- .../services/device-trust.service.spec.ts | 42 ++++++++++--------- .../services/master-password.service.ts | 16 ++----- libs/key-management/src/key.service.spec.ts | 20 +++++---- libs/key-management/src/key.service.ts | 5 +-- ...symmetric-key-regeneration.service.spec.ts | 3 ++ 10 files changed, 59 insertions(+), 56 deletions(-) diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index d92826765db..7172b98d727 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -357,7 +357,7 @@ export class NativeMessagingBackground { await this.secureCommunication(); } - return await this.encryptService.encrypt( + return await this.encryptService.encryptString( JSON.stringify(message), this.secureChannel!.sharedSecret!, ); @@ -401,10 +401,9 @@ export class NativeMessagingBackground { return; } message = JSON.parse( - await this.encryptService.decryptToUtf8( + await this.encryptService.decryptString( rawMessage as EncString, this.secureChannel.sharedSecret, - "ipc-desktop-ipc-channel-key", ), ); } else { diff --git a/apps/desktop/src/services/biometric-message-handler.service.spec.ts b/apps/desktop/src/services/biometric-message-handler.service.spec.ts index 9ddc3da8ed4..af18828b59d 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.spec.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.spec.ts @@ -347,7 +347,7 @@ describe("BiometricMessageHandlerService", () => { trusted: false, }), ); - encryptService.decryptToUtf8.mockResolvedValue( + encryptService.decryptString.mockResolvedValue( JSON.stringify({ command: "biometricUnlock", messageId: 0, @@ -382,7 +382,7 @@ describe("BiometricMessageHandlerService", () => { ngZone.run.mockReturnValue({ closed: of(true), }); - encryptService.decryptToUtf8.mockResolvedValue( + encryptService.decryptString.mockResolvedValue( JSON.stringify({ command: BiometricsCommands.UnlockWithBiometricsForUser, messageId: 0, @@ -433,7 +433,7 @@ describe("BiometricMessageHandlerService", () => { ngZone.run.mockReturnValue({ closed: of(false), }); - encryptService.decryptToUtf8.mockResolvedValue( + encryptService.decryptString.mockResolvedValue( JSON.stringify({ command: BiometricsCommands.UnlockWithBiometricsForUser, messageId: 0, @@ -480,7 +480,7 @@ describe("BiometricMessageHandlerService", () => { trusted: true, }), ); - encryptService.decryptToUtf8.mockResolvedValue( + encryptService.decryptString.mockResolvedValue( JSON.stringify({ command: BiometricsCommands.UnlockWithBiometricsForUser, messageId: 0, diff --git a/apps/desktop/src/services/biometric-message-handler.service.ts b/apps/desktop/src/services/biometric-message-handler.service.ts index 3851f40505b..42d7b8aae5f 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.ts @@ -175,7 +175,7 @@ export class BiometricMessageHandlerService { } const message: LegacyMessage = JSON.parse( - await this.encryptService.decryptToUtf8( + await this.encryptService.decryptString( rawMessage as EncString, SymmetricCryptoKey.fromString(sessionSecret), ), @@ -365,7 +365,7 @@ export class BiometricMessageHandlerService { throw new Error("Session secret is missing"); } - const encrypted = await this.encryptService.encrypt( + const encrypted = await this.encryptService.encryptString( JSON.stringify(message), SymmetricCryptoKey.fromString(sessionSecret), ); diff --git a/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts index 7a6c9bcd800..8bd58a21b6e 100644 --- a/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts +++ b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts @@ -49,6 +49,7 @@ export abstract class EncryptService { key: SymmetricCryptoKey, decryptTrace?: string, ): Promise; + /** * @deprecated Replaced by BulkEncryptService, remove once the feature is tested and the featureflag PM-4154-multi-worker-encryption-service is removed * @param items The items to decrypt diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts index 06999cab9c3..5e89e0a5cb7 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts @@ -209,7 +209,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { devices.data .filter((device) => device.isTrusted) .map(async (device) => { - const publicKey = await this.encryptService.decryptToBytes( + const publicKey = await this.encryptService.unwrapEncapsulationKey( new EncString(device.encryptedPublicKey), oldUserKey, ); @@ -220,7 +220,10 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { return null; } - const newEncryptedPublicKey = await this.encryptService.encrypt(publicKey, newUserKey); + const newEncryptedPublicKey = await this.encryptService.wrapEncapsulationKey( + publicKey, + newUserKey, + ); const newEncryptedUserKey = await this.encryptService.encapsulateKeyUnsigned( newUserKey, publicKey, @@ -278,7 +281,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { const currentDeviceKeys = await this.devicesApiService.getDeviceKeys(deviceIdentifier); // Decrypt the existing device public key with the old user key - const decryptedDevicePublicKey = await this.encryptService.decryptToBytes( + const decryptedDevicePublicKey = await this.encryptService.unwrapEncapsulationKey( currentDeviceKeys.encryptedPublicKey, oldUserKey, ); @@ -394,7 +397,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { try { // attempt to decrypt encryptedDevicePrivateKey with device key - const devicePrivateKey = await this.encryptService.decryptToBytes( + const devicePrivateKey = await this.encryptService.unwrapDecapsulationKey( encryptedDevicePrivateKey, deviceKey, ); diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts index 7d4a25c1f08..de9de5d781a 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts @@ -623,9 +623,9 @@ describe("deviceTrustService", () => { }); it("successfully returns the user key when provided keys (including device key) can decrypt it", async () => { - const decryptToBytesSpy = jest - .spyOn(encryptService, "decryptToBytes") - .mockResolvedValue(new Uint8Array(userKeyBytesLength)); + const unwrapDecapsulationKeySpy = jest + .spyOn(encryptService, "unwrapDecapsulationKey") + .mockResolvedValue(new Uint8Array(2048)); const rsaDecryptSpy = jest .spyOn(encryptService, "decapsulateKeyUnsigned") .mockResolvedValue(new SymmetricCryptoKey(new Uint8Array(userKeyBytesLength))); @@ -638,13 +638,13 @@ describe("deviceTrustService", () => { ); expect(result).toEqual(mockUserKey); - expect(decryptToBytesSpy).toHaveBeenCalledTimes(1); + expect(unwrapDecapsulationKeySpy).toHaveBeenCalledTimes(1); expect(rsaDecryptSpy).toHaveBeenCalledTimes(1); }); it("returns null and removes device key when the decryption fails", async () => { - const decryptToBytesSpy = jest - .spyOn(encryptService, "decryptToBytes") + const unwrapDecapsulationKeySpy = jest + .spyOn(encryptService, "unwrapDecapsulationKey") .mockRejectedValue(new Error("Decryption error")); const setDeviceKeySpy = jest.spyOn(deviceTrustService as any, "setDeviceKey"); @@ -656,7 +656,7 @@ describe("deviceTrustService", () => { ); expect(result).toBeNull(); - expect(decryptToBytesSpy).toHaveBeenCalledTimes(1); + expect(unwrapDecapsulationKeySpy).toHaveBeenCalledTimes(1); expect(setDeviceKeySpy).toHaveBeenCalledTimes(1); expect(setDeviceKeySpy).toHaveBeenCalledWith(mockUserId, null); }); @@ -704,8 +704,8 @@ describe("deviceTrustService", () => { DeviceResponse, ), ); - encryptService.decryptToBytes.mockResolvedValue(null); - encryptService.encrypt.mockResolvedValue(new EncString("test_encrypted_data")); + encryptService.decryptBytes.mockResolvedValue(null); + encryptService.encryptString.mockResolvedValue(new EncString("test_encrypted_data")); encryptService.encapsulateKeyUnsigned.mockResolvedValue( new EncString("test_encrypted_data"), ); @@ -752,9 +752,11 @@ describe("deviceTrustService", () => { DeviceResponse, ), ); - encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(64)); - encryptService.encrypt.mockResolvedValue(new EncString("test_encrypted_data")); - encryptService.rsaEncrypt.mockResolvedValue(new EncString("test_encrypted_data")); + encryptService.unwrapEncapsulationKey.mockResolvedValue(new Uint8Array(64)); + encryptService.wrapEncapsulationKey.mockResolvedValue(new EncString("test_encrypted_data")); + encryptService.encapsulateKeyUnsigned.mockResolvedValue( + new EncString("test_encrypted_data"), + ); const protectedDeviceResponse = new ProtectedDeviceResponse({ id: "", @@ -862,13 +864,15 @@ describe("deviceTrustService", () => { }); // Mock the decryption of the public key with the old user key - encryptService.decryptToBytes.mockImplementationOnce((_encValue, privateKeyValue) => { - expect(privateKeyValue.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64); - expect(new Uint8Array(privateKeyValue.toEncoded())[0]).toBe(FakeOldUserKeyMarker); - const data = new Uint8Array(250); - data.fill(FakeDecryptedPublicKeyMarker, 0, 1); - return Promise.resolve(data); - }); + encryptService.unwrapEncapsulationKey.mockImplementationOnce( + (_encValue, privateKeyValue) => { + expect(privateKeyValue.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64); + expect(new Uint8Array(privateKeyValue.toEncoded())[0]).toBe(FakeOldUserKeyMarker); + const data = new Uint8Array(250); + data.fill(FakeDecryptedPublicKeyMarker, 0, 1); + return Promise.resolve(data); + }, + ); // Mock the encryption of the new user key with the decrypted public key encryptService.encapsulateKeyUnsigned.mockImplementationOnce((data, publicKey) => { diff --git a/libs/common/src/key-management/master-password/services/master-password.service.ts b/libs/common/src/key-management/master-password/services/master-password.service.ts index 72b18d8bfba..b9b11d6cbe8 100644 --- a/libs/common/src/key-management/master-password/services/master-password.service.ts +++ b/libs/common/src/key-management/master-password/services/master-password.service.ts @@ -174,21 +174,13 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr throw new Error("No master key found."); } - let decUserKey: Uint8Array; + let decUserKey: SymmetricCryptoKey; if (userKey.encryptionType === EncryptionType.AesCbc256_B64) { - decUserKey = await this.encryptService.decryptToBytes( - userKey, - masterKey, - "Content: User Key; Encrypting Key: Master Key", - ); + decUserKey = await this.encryptService.unwrapSymmetricKey(userKey, masterKey); } else if (userKey.encryptionType === EncryptionType.AesCbc256_HmacSha256_B64) { const newKey = await this.keyGenerationService.stretchKey(masterKey); - decUserKey = await this.encryptService.decryptToBytes( - userKey, - newKey, - "Content: User Key; Encrypting Key: Stretched Master Key", - ); + decUserKey = await this.encryptService.unwrapSymmetricKey(userKey, newKey); } else { throw new Error("Unsupported encryption type."); } @@ -198,6 +190,6 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr return null; } - return new SymmetricCryptoKey(decUserKey) as UserKey; + return decUserKey as UserKey; } } diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index 57291462924..25d8aff99fb 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -330,7 +330,7 @@ describe("keyService", () => { everHadUserKeyState.nextState(null); // Mock private key decryption - encryptService.decryptToBytes.mockResolvedValue(mockRandomBytes); + encryptService.unwrapDecapsulationKey.mockResolvedValue(mockRandomBytes); }); it("throws if userKey is null", async () => { @@ -352,7 +352,7 @@ describe("keyService", () => { }); it("throws if encPrivateKey cannot be decrypted with the userKey", async () => { - encryptService.decryptToBytes.mockResolvedValue(null); + encryptService.unwrapDecapsulationKey.mockResolvedValue(null); await expect( keyService.setUserKeys(mockUserKey, mockEncPrivateKey, mockUserId), @@ -452,17 +452,16 @@ describe("keyService", () => { // Decryption of the user private key const fakeDecryptedUserPrivateKey = makeStaticByteArray(10, 1); - encryptService.decryptToBytes.mockResolvedValue(fakeDecryptedUserPrivateKey); + encryptService.unwrapDecapsulationKey.mockResolvedValue(fakeDecryptedUserPrivateKey); const fakeUserPublicKey = makeStaticByteArray(10, 2); cryptoFunctionService.rsaExtractPublicKey.mockResolvedValue(fakeUserPublicKey); const userPrivateKey = await firstValueFrom(keyService.userPrivateKey$(mockUserId)); - expect(encryptService.decryptToBytes).toHaveBeenCalledWith( + expect(encryptService.unwrapDecapsulationKey).toHaveBeenCalledWith( fakeEncryptedUserPrivateKey, userKey, - "Content: Encrypted Private Key", ); expect(userPrivateKey).toBe(fakeDecryptedUserPrivateKey); @@ -473,7 +472,7 @@ describe("keyService", () => { const userPrivateKey = await firstValueFrom(keyService.userPrivateKey$(mockUserId)); - expect(encryptService.decryptToBytes).not.toHaveBeenCalled(); + expect(encryptService.unwrapDecapsulationKey).not.toHaveBeenCalled(); expect(userPrivateKey).toBeFalsy(); }); @@ -552,10 +551,12 @@ describe("keyService", () => { providerKeysState.nextState(keys.providerKeys!); } - encryptService.decryptToBytes.mockImplementation((encryptedPrivateKey, userKey) => { - // TOOD: Branch between provider and private key? + encryptService.unwrapDecapsulationKey.mockImplementation((encryptedPrivateKey, userKey) => { return Promise.resolve(fakePrivateKeyDecryption(encryptedPrivateKey, userKey)); }); + encryptService.unwrapSymmetricKey.mockImplementation((encryptedPrivateKey, userKey) => { + return Promise.resolve(new SymmetricCryptoKey(new Uint8Array(64))); + }); encryptService.decapsulateKeyUnsigned.mockImplementation((data, privateKey) => { return Promise.resolve(new SymmetricCryptoKey(fakeOrgKeyDecryption(data, privateKey))); @@ -617,6 +618,7 @@ describe("keyService", () => { }); it("returns decryption keys when some of the org keys are providers", async () => { + encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(64)); const org2Id = "org2Id" as OrganizationId; updateKeys({ userKey: makeSymmetricCryptoKey(64), @@ -647,7 +649,7 @@ describe("keyService", () => { const org2Key = decryptionKeys!.orgKeys![org2Id]; expect(org2Key).not.toBeNull(); - expect(org2Key.keyB64).toContain("provider1Key"); + expect(org2Key.toEncoded()).toHaveLength(64); }); it("returns a stream that pays attention to updates of all data", async () => { diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index 01e7b713e1b..849b9db6a50 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -738,7 +738,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { const storePin = await this.shouldStoreKey(KeySuffixOptions.Pin, userId); if (storePin) { // Decrypt userKeyEncryptedPin with user key - const pin = await this.encryptService.decryptToUtf8( + const pin = await this.encryptService.decryptString( (await this.pinService.getUserKeyEncryptedPin(userId))!, key, ); @@ -945,10 +945,9 @@ export class DefaultKeyService implements KeyServiceAbstraction { return null; } - return (await this.encryptService.decryptToBytes( + return (await this.encryptService.unwrapDecapsulationKey( new EncString(encryptedPrivateKey), key, - "Content: Encrypted Private Key", )) as UserPrivateKey; } diff --git a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts index 8e2ce8ab6b9..35cef914588 100644 --- a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts +++ b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts @@ -58,6 +58,9 @@ function setupUserKeyValidation( cipher.notes = mockEnc("EncryptedString"); cipher.key = mockEnc("EncKey"); cipherService.getAll.mockResolvedValue([cipher]); + encryptService.unwrapSymmetricKey.mockResolvedValue( + new SymmetricCryptoKey(makeStaticByteArray(64)), + ); encryptService.decryptToBytes.mockResolvedValue(makeStaticByteArray(64)); encryptService.decryptString.mockResolvedValue("mockDecryptedString"); (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); From fcaf5e63c5e8a54dc3c65d0cb16c9c6f26379bfe Mon Sep 17 00:00:00 2001 From: Matt Andreko Date: Mon, 12 May 2025 07:56:50 -0400 Subject: [PATCH 012/245] Duo URL redirect enhancements (#14640) * Provide additional scrutiny on Duo redirect filtering * Address review feedback from Jared * Add documentation to redirectToDuoFrameless method --- apps/web/src/connectors/duo-redirect.spec.ts | 46 ++++++++++++++++++-- apps/web/src/connectors/duo-redirect.ts | 39 ++++++++++++----- 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/apps/web/src/connectors/duo-redirect.spec.ts b/apps/web/src/connectors/duo-redirect.spec.ts index c0498861ba0..85953a5dd00 100644 --- a/apps/web/src/connectors/duo-redirect.spec.ts +++ b/apps/web/src/connectors/duo-redirect.spec.ts @@ -10,13 +10,13 @@ describe("duo-redirect", () => { }); it("should redirect to a valid Duo URL", () => { - const validUrl = "https://api-123.duosecurity.com/auth"; + const validUrl = "https://api-123.duosecurity.com/oauth/v1/authorize"; redirectToDuoFrameless(validUrl); expect(window.location.href).toBe(validUrl); }); it("should redirect to a valid Duo Federal URL", () => { - const validUrl = "https://api-123.duofederal.com/auth"; + const validUrl = "https://api-123.duofederal.com/oauth/v1/authorize"; redirectToDuoFrameless(validUrl); expect(window.location.href).toBe(validUrl); }); @@ -27,15 +27,55 @@ describe("duo-redirect", () => { }); it("should throw an error for an malicious URL with valid redirect embedded", () => { - const invalidUrl = "https://malicious-site.com\\@api-123.duosecurity.com/auth"; + const invalidUrl = "https://malicious-site.com\\@api-123.duosecurity.com/oauth/v1/authorize"; expect(() => redirectToDuoFrameless(invalidUrl)).toThrow("Invalid redirect URL"); }); + it("should throw an error for a URL with a malicious subdomain", () => { + const maliciousSubdomainUrl = + "https://api-a86d5bde.duosecurity.com.evil.com/oauth/v1/authorize"; + expect(() => redirectToDuoFrameless(maliciousSubdomainUrl)).toThrow("Invalid redirect URL"); + }); + + it("should throw an error for a URL using HTTP protocol", () => { + const maliciousSubdomainUrl = "http://api-a86d5bde.duosecurity.com/oauth/v1/authorize"; + expect(() => redirectToDuoFrameless(maliciousSubdomainUrl)).toThrow( + "Invalid redirect URL: invalid protocol", + ); + }); + + it("should throw an error for a URL with javascript code", () => { + const maliciousSubdomainUrl = "javascript://https://api-a86d5bde.duosecurity.com%0Aalert(1)"; + expect(() => redirectToDuoFrameless(maliciousSubdomainUrl)).toThrow( + "Invalid redirect URL: invalid protocol", + ); + }); + it("should throw an error for a non-HTTPS URL", () => { const nonHttpsUrl = "http://api-123.duosecurity.com/auth"; expect(() => redirectToDuoFrameless(nonHttpsUrl)).toThrow("Invalid redirect URL"); }); + it("should throw an error for a URL with invalid port specified", () => { + const urlWithPort = "https://api-123.duyosecurity.com:8080/auth"; + expect(() => redirectToDuoFrameless(urlWithPort)).toThrow( + "Invalid redirect URL: port not allowed", + ); + }); + + it("should redirect to a valid Duo Federal URL with valid port", () => { + const validUrl = "https://api-123.duofederal.com:443/oauth/v1/authorize"; + redirectToDuoFrameless(validUrl); + expect(window.location.href).toBe(validUrl); + }); + + it("should throw an error for a URL with an invalid pathname", () => { + const urlWithPort = "https://api-123.duyosecurity.com/../evil/path/here/"; + expect(() => redirectToDuoFrameless(urlWithPort)).toThrow( + "Invalid redirect URL: invalid pathname", + ); + }); + it("should throw an error for a URL with an invalid hostname", () => { const invalidHostnameUrl = "https://api-123.invalid.com"; expect(() => redirectToDuoFrameless(invalidHostnameUrl)).toThrow("Invalid redirect URL"); diff --git a/apps/web/src/connectors/duo-redirect.ts b/apps/web/src/connectors/duo-redirect.ts index d1841247962..5389b31f6af 100644 --- a/apps/web/src/connectors/duo-redirect.ts +++ b/apps/web/src/connectors/duo-redirect.ts @@ -57,29 +57,46 @@ window.addEventListener("load", async () => { * @param redirectUrl the duo auth url */ export function redirectToDuoFrameless(redirectUrl: string) { - // Regex to match a valid duo redirect URL + // Validation for Duo redirect URL to prevent open redirect or XSS vulnerabilities + // Only used for Duo 2FA redirects in the extension /** * This regex checks for the following: - * The string must start with "https://api-" - * Followed by a subdomain that can contain letters, numbers + * The hostname must start with a subdomain that begins with "api-" followed by a + * string that can contain letters or numbers of indeterminate length * Followed by either "duosecurity.com" or "duofederal.com" * This ensures that the redirect does not contain any malicious content * and is a valid Duo URL. * */ - const duoRedirectUrlRegex = /^https:\/\/api-[a-zA-Z0-9]+\.(duosecurity|duofederal)\.com/; - // Check if the redirect URL matches the regex - if (!duoRedirectUrlRegex.test(redirectUrl)) { - throw new Error("Invalid redirect URL"); - } - // At this point we know the URL to be valid, but we need to check for embedded credentials + const duoRedirectUrlRegex = /^api-[a-zA-Z0-9]+\.(duosecurity|duofederal)\.com$/; const validateUrl = new URL(redirectUrl); - // URLs should not contain + // Check that no embedded credentials are present if (validateUrl.username || validateUrl.password) { throw new Error("Invalid redirect URL: embedded credentials not allowed"); } - window.location.href = decodeURIComponent(redirectUrl); + // Check that the protocol is HTTPS + if (validateUrl.protocol !== "https:") { + throw new Error("Invalid redirect URL: invalid protocol"); + } + + // Check that the port is not specified + if (validateUrl.port && validateUrl.port !== "443") { + throw new Error("Invalid redirect URL: port not allowed"); + } + + if (validateUrl.pathname !== "/oauth/v1/authorize") { + throw new Error("Invalid redirect URL: invalid pathname"); + } + + // Check if the redirect hostname matches the regex + // Only check the hostname part of the URL to avoid over-zealous Regex expressions from matching + // and causing an Open Redirect vulnerability. Always use hostname instead of host, because host includes port if specified. + if (!duoRedirectUrlRegex.test(validateUrl.hostname)) { + throw new Error("Invalid redirect URL"); + } + + window.location.href = redirectUrl; } /** From 4d15f2d43cbd79ac29b35c8e8e3244ecb59ad8e4 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham Date: Mon, 12 May 2025 09:00:01 -0400 Subject: [PATCH 013/245] [PM-16057] use Roboto as primary font (#14553) * swap to using roboto variable font --- .../content/components/constants/styles.ts | 6 +++--- .../notification/confirmation/message.ts | 2 +- .../components/notification/header-message.ts | 2 +- .../src/autofill/shared/styles/variables.scss | 2 +- apps/browser/src/popup/images/loading.svg | 2 +- apps/browser/src/popup/scss/variables.scss | 2 +- .../src/app/components/avatar.component.ts | 2 +- apps/desktop/src/images/loading.svg | 2 +- apps/desktop/src/scss/variables.scss | 2 +- .../src/app/billing/services/stripe.service.ts | 2 +- apps/web/src/images/loading-white.svg | 2 +- apps/web/src/images/loading.svg | 2 +- apps/web/src/scss/variables.scss | 2 +- libs/angular/src/scss/webfonts.css | 6 +++--- libs/angular/src/scss/webfonts/dm-sans.woff2 | Bin 88504 -> 0 bytes libs/angular/src/scss/webfonts/roboto.woff2 | Bin 0 -> 209484 bytes libs/components/src/avatar/avatar.component.ts | 2 +- libs/components/src/variables.scss | 2 +- 18 files changed, 20 insertions(+), 20 deletions(-) delete mode 100644 libs/angular/src/scss/webfonts/dm-sans.woff2 create mode 100644 libs/angular/src/scss/webfonts/roboto.woff2 diff --git a/apps/browser/src/autofill/content/components/constants/styles.ts b/apps/browser/src/autofill/content/components/constants/styles.ts index f7c9ffd4d92..08c8671ce14 100644 --- a/apps/browser/src/autofill/content/components/constants/styles.ts +++ b/apps/browser/src/autofill/content/components/constants/styles.ts @@ -144,17 +144,17 @@ export const border = { export const typography = { body1: ` line-height: 24px; - font-family: "DM Sans", sans-serif; + font-family: Roboto, sans-serif; font-size: 16px; `, body2: ` line-height: 20px; - font-family: "DM Sans", sans-serif; + font-family: Roboto, sans-serif; font-size: 14px; `, helperMedium: ` line-height: 16px; - font-family: "DM Sans", sans-serif; + font-family: Roboto, sans-serif; font-size: 12px; `, }; diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts index 521fdf45291..527119aed15 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts @@ -72,7 +72,7 @@ const baseTextStyles = css` text-align: left; text-overflow: ellipsis; line-height: 24px; - font-family: "DM Sans", sans-serif; + font-family: Roboto, sans-serif; font-size: 16px; `; diff --git a/apps/browser/src/autofill/content/components/notification/header-message.ts b/apps/browser/src/autofill/content/components/notification/header-message.ts index ccfa58b8970..47fe8cd2828 100644 --- a/apps/browser/src/autofill/content/components/notification/header-message.ts +++ b/apps/browser/src/autofill/content/components/notification/header-message.ts @@ -19,7 +19,7 @@ const notificationHeaderMessageStyles = (theme: Theme) => css` line-height: 28px; white-space: nowrap; color: ${themes[theme].text.main}; - font-family: "DM Sans", sans-serif; + font-family: Roboto, sans-serif; font-size: 18px; font-weight: 600; `; diff --git a/apps/browser/src/autofill/shared/styles/variables.scss b/apps/browser/src/autofill/shared/styles/variables.scss index ae6a060798a..1e804ed8fd2 100644 --- a/apps/browser/src/autofill/shared/styles/variables.scss +++ b/apps/browser/src/autofill/shared/styles/variables.scss @@ -1,6 +1,6 @@ $dark-icon-themes: "theme_dark"; -$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-sans-serif: Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-source-code-pro: "Source Code Pro", monospace; $font-size-base: 14px; diff --git a/apps/browser/src/popup/images/loading.svg b/apps/browser/src/popup/images/loading.svg index 3f2033303db..5f4102a5921 100644 --- a/apps/browser/src/popup/images/loading.svg +++ b/apps/browser/src/popup/images/loading.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/browser/src/popup/scss/variables.scss b/apps/browser/src/popup/scss/variables.scss index b78f06f2f3f..aea69e26436 100644 --- a/apps/browser/src/popup/scss/variables.scss +++ b/apps/browser/src/popup/scss/variables.scss @@ -1,6 +1,6 @@ $dark-icon-themes: "theme_dark"; -$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-sans-serif: Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; $font-size-base: 16px; $font-size-large: 18px; diff --git a/apps/desktop/src/app/components/avatar.component.ts b/apps/desktop/src/app/components/avatar.component.ts index 87db2e4b6af..d2660763667 100644 --- a/apps/desktop/src/app/components/avatar.component.ts +++ b/apps/desktop/src/app/components/avatar.component.ts @@ -113,7 +113,7 @@ export class AvatarComponent implements OnChanges, OnInit { textTag.setAttribute("fill", Utils.pickTextColorBasedOnBgColor(color, 135, true)); textTag.setAttribute( "font-family", - '"DM Sans","Helvetica Neue",Helvetica,Arial,' + + 'Roboto,"Helvetica Neue",Helvetica,Arial,' + 'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"', ); textTag.textContent = character; diff --git a/apps/desktop/src/images/loading.svg b/apps/desktop/src/images/loading.svg index 3f2033303db..5f4102a5921 100644 --- a/apps/desktop/src/images/loading.svg +++ b/apps/desktop/src/images/loading.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/desktop/src/scss/variables.scss b/apps/desktop/src/scss/variables.scss index b8978e284e5..b094be63f8c 100644 --- a/apps/desktop/src/scss/variables.scss +++ b/apps/desktop/src/scss/variables.scss @@ -1,6 +1,6 @@ $dark-icon-themes: "theme_dark"; -$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-sans-serif: Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; $font-size-base: 14px; $font-size-large: 18px; diff --git a/apps/web/src/app/billing/services/stripe.service.ts b/apps/web/src/app/billing/services/stripe.service.ts index 5c35923c1f4..360187ecd1e 100644 --- a/apps/web/src/app/billing/services/stripe.service.ts +++ b/apps/web/src/app/billing/services/stripe.service.ts @@ -148,7 +148,7 @@ export class StripeService { base: { color: null, fontFamily: - '"DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, ' + + 'Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif, ' + '"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"', fontSize: "16px", fontSmoothing: "antialiased", diff --git a/apps/web/src/images/loading-white.svg b/apps/web/src/images/loading-white.svg index 2bebff7daba..ef5970da42e 100644 --- a/apps/web/src/images/loading-white.svg +++ b/apps/web/src/images/loading-white.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/web/src/images/loading.svg b/apps/web/src/images/loading.svg index 3f2033303db..5f4102a5921 100644 --- a/apps/web/src/images/loading.svg +++ b/apps/web/src/images/loading.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/web/src/scss/variables.scss b/apps/web/src/scss/variables.scss index 4b023e12746..66773999c54 100644 --- a/apps/web/src/scss/variables.scss +++ b/apps/web/src/scss/variables.scss @@ -21,7 +21,7 @@ $body-bg: $white; $body-color: #333333; $font-family-sans-serif: - "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; $h1-font-size: 1.7rem; diff --git a/libs/angular/src/scss/webfonts.css b/libs/angular/src/scss/webfonts.css index 04b072e1bf1..cd35271bf80 100644 --- a/libs/angular/src/scss/webfonts.css +++ b/libs/angular/src/scss/webfonts.css @@ -1,8 +1,8 @@ @font-face { - font-family: "DM Sans"; + font-family: Roboto; src: - url("webfonts/dm-sans.woff2") format("woff2 supports variations"), - url("webfonts/dm-sans.woff2") format("woff2-variations"); + url("webfonts/roboto.woff2") format("woff2 supports variations"), + url("webfonts/roboto.woff2") format("woff2-variations"); font-display: swap; font-weight: 100 900; } diff --git a/libs/angular/src/scss/webfonts/dm-sans.woff2 b/libs/angular/src/scss/webfonts/dm-sans.woff2 deleted file mode 100644 index 2375279dbf5a47d05c9b3364961a1f2d768b78cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88504 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�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�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 B`$zx) diff --git a/libs/angular/src/scss/webfonts/roboto.woff2 b/libs/angular/src/scss/webfonts/roboto.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..af8d2343d9c7d72c6fcbf7ffe127d0cc9f3e696c GIT binary patch literal 209484 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
${message || buttonText ? html` - ${itemName} - - ${message || nothing} - ${buttonText - ? html` - handleButtonKeyDown(e, () => handleClick(e))} - aria-label=${buttonAria} - tabindex="0" - role="button" - > - ${buttonText} - - ` - : nothing} - +
+ ${itemName} + + ${message || nothing} + ${buttonText + ? html` + + handleButtonKeyDown(e, () => handleClick(e))} + aria-label=${buttonAria} + tabindex="0" + role="button" + > + ${buttonText} + + ` + : nothing} + +
` : nothing} ${messageDetails @@ -61,12 +64,17 @@ export function NotificationConfirmationMessage({ const containerStyles = css` display: flex; - flex-wrap: wrap; - align-items: center; + flex-direction: column; gap: ${spacing[1]}; width: 100%; `; +const singleLineWrapperStyles = css` + display: inline; + white-space: normal; + word-break: break-word; +`; + const baseTextStyles = css` overflow-x: hidden; text-align: left; @@ -81,6 +89,9 @@ const notificationConfirmationMessageStyles = (theme: Theme) => css` color: ${themes[theme].text.main}; font-weight: 400; + white-space: normal; + word-break: break-word; + display: inline; `; const itemNameStyles = (theme: Theme) => css` @@ -90,6 +101,10 @@ const itemNameStyles = (theme: Theme) => css` font-weight: 400; white-space: nowrap; max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + vertical-align: bottom; `; const notificationConfirmationButtonTextStyles = (theme: Theme) => css` diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 3fd6016eb33..52cbdcd47d3 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -59,9 +59,7 @@ function getI18n() { collection: chrome.i18n.getMessage("collection"), folder: chrome.i18n.getMessage("folder"), loginSaveSuccess: chrome.i18n.getMessage("loginSaveSuccess"), - loginSaveConfirmation: chrome.i18n.getMessage("loginSaveConfirmation"), loginUpdateSuccess: chrome.i18n.getMessage("loginUpdateSuccess"), - loginUpdatedConfirmation: chrome.i18n.getMessage("loginUpdatedConfirmation"), loginUpdateTaskSuccess: chrome.i18n.getMessage("loginUpdateTaskSuccess"), loginUpdateTaskSuccessAdditional: chrome.i18n.getMessage("loginUpdateTaskSuccessAdditional"), nextSecurityTaskAction: chrome.i18n.getMessage("nextSecurityTaskAction"), @@ -74,6 +72,10 @@ function getI18n() { notificationUpdate: chrome.i18n.getMessage("notificationChangeSave"), notificationEdit: chrome.i18n.getMessage("edit"), notificationEditTooltip: chrome.i18n.getMessage("notificationEditTooltip"), + notificationLoginSaveConfirmation: chrome.i18n.getMessage("notificationLoginSaveConfirmation"), + notificationLoginUpdatedConfirmation: chrome.i18n.getMessage( + "notificationLoginUpdatedConfirmation", + ), notificationUnlock: chrome.i18n.getMessage("notificationUnlock"), notificationUnlockDesc: chrome.i18n.getMessage("notificationUnlockDesc"), notificationViewAria: chrome.i18n.getMessage("notificationViewAria"), From 02cbc37ac56eccefacfb1d35a6a4c7e8f59c0cea Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Mon, 12 May 2025 17:23:06 -0400 Subject: [PATCH 030/245] PM-20397 Misc notification bar UI tweaks (#14558) * PM-20637 * turncate save button --- .../content/components/notification/container.ts | 2 +- .../content/components/notification/footer.ts | 15 +++++++++------ .../content/components/rows/button-row.ts | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/apps/browser/src/autofill/content/components/notification/container.ts b/apps/browser/src/autofill/content/components/notification/container.ts index c7c7ae16cde..1a78854f5dc 100644 --- a/apps/browser/src/autofill/content/components/notification/container.ts +++ b/apps/browser/src/autofill/content/components/notification/container.ts @@ -94,7 +94,7 @@ const notificationContainerStyles = (theme: Theme) => css` } [class*="${notificationBodyClassPrefix}-"] { - margin: ${spacing["3"]} 0 ${spacing["1.5"]} ${spacing["3"]}; + margin: ${spacing["3"]} 0 0 ${spacing["3"]}; padding-right: ${spacing["3"]}; } `; diff --git a/apps/browser/src/autofill/content/components/notification/footer.ts b/apps/browser/src/autofill/content/components/notification/footer.ts index 73fe9084595..03d0d475e85 100644 --- a/apps/browser/src/autofill/content/components/notification/footer.ts +++ b/apps/browser/src/autofill/content/components/notification/footer.ts @@ -8,7 +8,7 @@ import { NotificationTypes, } from "../../../notification/abstractions/notification-bar"; import { OrgView, FolderView, I18n, CollectionView } from "../common-types"; -import { spacing, themes } from "../constants/styles"; +import { spacing } from "../constants/styles"; import { NotificationButtonRow } from "./button-row"; @@ -37,7 +37,7 @@ export function NotificationFooter({ const primaryButtonText = i18n.saveAction; return html` -
+
${!isChangeNotification ? NotificationButtonRow({ collections, @@ -56,13 +56,16 @@ export function NotificationFooter({ `; } -const notificationFooterStyles = ({ theme }: { theme: Theme }) => css` +const notificationFooterStyles = ({ + isChangeNotification, +}: { + isChangeNotification: boolean; +}) => css` display: flex; - background-color: ${themes[theme].background.alt}; - padding: 0 ${spacing[3]} ${spacing[3]} ${spacing[3]}; + padding: ${spacing[2]} ${spacing[4]} ${isChangeNotification ? spacing[1] : spacing[4]} + ${spacing[4]}; :last-child { border-radius: 0 0 ${spacing["4"]} ${spacing["4"]}; - padding-bottom: ${spacing[4]}; } `; diff --git a/apps/browser/src/autofill/content/components/rows/button-row.ts b/apps/browser/src/autofill/content/components/rows/button-row.ts index 3527d050b81..7e81d846cb7 100644 --- a/apps/browser/src/autofill/content/components/rows/button-row.ts +++ b/apps/browser/src/autofill/content/components/rows/button-row.ts @@ -62,7 +62,7 @@ const buttonRowStyles = css` > button { max-width: min-content; - flex: 1 1 50%; + flex: 1 1 25%; } > div { From 7a51161d50da1b1b4e2428de943a8a83991bde82 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 13 May 2025 12:43:26 +0200 Subject: [PATCH 031/245] Move selectable avatar to auth (#14704) --- .../settings/account}/selectable-avatar.component.ts | 0 apps/web/src/app/shared/loose-components.module.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename apps/web/src/app/{components => auth/settings/account}/selectable-avatar.component.ts (100%) diff --git a/apps/web/src/app/components/selectable-avatar.component.ts b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts similarity index 100% rename from apps/web/src/app/components/selectable-avatar.component.ts rename to apps/web/src/app/auth/settings/account/selectable-avatar.component.ts diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index 30d1dd1af75..ab6c6cc5d72 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -22,6 +22,7 @@ import { DangerZoneComponent } from "../auth/settings/account/danger-zone.compon import { DeauthorizeSessionsComponent } from "../auth/settings/account/deauthorize-sessions.component"; import { DeleteAccountDialogComponent } from "../auth/settings/account/delete-account-dialog.component"; import { ProfileComponent } from "../auth/settings/account/profile.component"; +import { SelectableAvatarComponent } from "../auth/settings/account/selectable-avatar.component"; import { EmergencyAccessConfirmComponent } from "../auth/settings/emergency-access/confirm/emergency-access-confirm.component"; import { EmergencyAccessAddEditComponent } from "../auth/settings/emergency-access/emergency-access-add-edit.component"; import { EmergencyAccessComponent } from "../auth/settings/emergency-access/emergency-access.component"; @@ -39,7 +40,6 @@ import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.comp import { SponsoredFamiliesComponent } from "../billing/settings/sponsored-families.component"; import { SponsoringOrgRowComponent } from "../billing/settings/sponsoring-org-row.component"; import { DynamicAvatarComponent } from "../components/dynamic-avatar.component"; -import { SelectableAvatarComponent } from "../components/selectable-avatar.component"; import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "../dirt/reports/pages/organizations/exposed-passwords-report.component"; import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "../dirt/reports/pages/organizations/inactive-two-factor-report.component"; import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "../dirt/reports/pages/organizations/reused-passwords-report.component"; From 4cf6e19b305c693e250f9e4a29008e8f3d06c464 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Tue, 13 May 2025 09:28:25 -0400 Subject: [PATCH 032/245] Estimate tax for trial initiation flow when trial length is 0 (#14674) --- .../trial-billing-step.component.html | 41 +++++++- .../trial-billing-step.component.ts | 94 ++++++++++++++++++- .../abstractions/tax.service.abstraction.ts | 6 ++ .../src/billing/models/request/tax/index.ts | 1 + ...x-amount-for-organization-trial.request.ts | 11 +++ .../src/billing/models/response/tax/index.ts | 1 + .../tax/preview-tax-amount.response.ts | 11 +++ .../src/billing/services/tax.service.ts | 15 +++ 8 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 libs/common/src/billing/models/request/tax/index.ts create mode 100644 libs/common/src/billing/models/request/tax/preview-tax-amount-for-organization-trial.request.ts create mode 100644 libs/common/src/billing/models/response/tax/index.ts create mode 100644 libs/common/src/billing/models/response/tax/preview-tax-amount.response.ts diff --git a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html index d947ea96dfb..64a9781b7cf 100644 --- a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html +++ b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html @@ -51,8 +51,38 @@

{{ "paymentType" | i18n }}

+ + @if (trialLength === 0) { + @let priceLabel = + subscriptionProduct === SubscriptionProduct.PasswordManager + ? "passwordManagerPlanPrice" + : "secretsManagerPlanPrice"; + +
+
+ {{ priceLabel | i18n }}: {{ getPriceFor(formGroup.value.cadence) | currency: "USD $" }} +
+ {{ "estimatedTax" | i18n }}: + @if (fetchingTaxAmount) { + + } @else { + {{ taxAmount | currency: "USD $" }} + } +
+
+
+

+ {{ "total" | i18n }}: + @if (fetchingTaxAmount) { + + } @else { + {{ total | currency: "USD $" }}/{{ interval | i18n }} + } +

+
+ }
+ + + + {{ "loading" | i18n }} + diff --git a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts index c6248a06a89..614d8bf5f97 100644 --- a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts +++ b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts @@ -1,7 +1,16 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from "@angular/core"; +import { + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; +import { from, Subject, switchMap, takeUntil } from "rxjs"; import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -12,7 +21,14 @@ import { PaymentInformation, PlanInformation, } from "@bitwarden/common/billing/abstractions/organization-billing.service"; -import { PaymentMethodType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; +import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; +import { + PaymentMethodType, + PlanType, + ProductTierType, + ProductType, +} from "@bitwarden/common/billing/enums"; +import { PreviewTaxAmountForOrganizationTrialRequest } from "@bitwarden/common/billing/models/request/tax"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -50,7 +66,7 @@ export enum SubscriptionProduct { imports: [BillingSharedModule], standalone: true, }) -export class TrialBillingStepComponent implements OnInit { +export class TrialBillingStepComponent implements OnInit, OnDestroy { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; @ViewChild(ManageTaxInformationComponent) taxInfoComponent: ManageTaxInformationComponent; @Input() organizationInfo: OrganizationInfo; @@ -60,6 +76,7 @@ export class TrialBillingStepComponent implements OnInit { @Output() organizationCreated = new EventEmitter(); loading = true; + fetchingTaxAmount = false; annualCadence = SubscriptionCadence.Annual; monthlyCadence = SubscriptionCadence.Monthly; @@ -73,6 +90,12 @@ export class TrialBillingStepComponent implements OnInit { annualPlan?: PlanResponse; monthlyPlan?: PlanResponse; + taxAmount = 0; + + private destroy$ = new Subject(); + + protected readonly SubscriptionProduct = SubscriptionProduct; + constructor( private apiService: ApiService, private i18nService: I18nService, @@ -80,6 +103,7 @@ export class TrialBillingStepComponent implements OnInit { private messagingService: MessagingService, private organizationBillingService: OrganizationBillingService, private toastService: ToastService, + private taxService: TaxServiceAbstraction, ) {} async ngOnInit(): Promise { @@ -87,9 +111,26 @@ export class TrialBillingStepComponent implements OnInit { this.applicablePlans = plans.data.filter(this.isApplicable); this.annualPlan = this.findPlanFor(SubscriptionCadence.Annual); this.monthlyPlan = this.findPlanFor(SubscriptionCadence.Monthly); + + if (this.trialLength === 0) { + this.formGroup.controls.cadence.valueChanges + .pipe( + switchMap((cadence) => from(this.previewTaxAmount(cadence))), + takeUntil(this.destroy$), + ) + .subscribe((taxAmount) => { + this.taxAmount = taxAmount; + }); + } + this.loading = false; } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + async submit(): Promise { if (!this.taxInfoComponent.validate()) { return; @@ -115,7 +156,11 @@ export class TrialBillingStepComponent implements OnInit { this.messagingService.send("organizationCreated", { organizationId }); } - protected changedCountry() { + async onTaxInformationChanged() { + if (this.trialLength === 0) { + this.taxAmount = await this.previewTaxAmount(this.formGroup.value.cadence); + } + this.paymentComponent.showBankAccount = this.taxInfoComponent.getTaxInformation().country === "US"; if ( @@ -250,4 +295,45 @@ export class TrialBillingStepComponent implements OnInit { const notDisabledOrLegacy = !plan.disabled && !plan.legacyYear; return hasCorrectProductType && notDisabledOrLegacy; } + + private previewTaxAmount = async (cadence: SubscriptionCadence): Promise => { + this.fetchingTaxAmount = true; + + if (!this.taxInfoComponent.validate()) { + return 0; + } + + const plan = this.findPlanFor(cadence); + + const productType = + this.subscriptionProduct === SubscriptionProduct.PasswordManager + ? ProductType.PasswordManager + : ProductType.SecretsManager; + + const taxInformation = this.taxInfoComponent.getTaxInformation(); + + const request: PreviewTaxAmountForOrganizationTrialRequest = { + planType: plan.type, + productType, + taxInformation: { + ...taxInformation, + }, + }; + + const response = await this.taxService.previewTaxAmountForOrganizationTrial(request); + this.fetchingTaxAmount = false; + return response.taxAmount; + }; + + get price() { + return this.getPriceFor(this.formGroup.value.cadence); + } + + get total() { + return this.price + this.taxAmount; + } + + get interval() { + return this.formGroup.value.cadence === SubscriptionCadence.Annual ? "year" : "month"; + } } diff --git a/libs/common/src/billing/abstractions/tax.service.abstraction.ts b/libs/common/src/billing/abstractions/tax.service.abstraction.ts index 7a744dae856..73dc848c95f 100644 --- a/libs/common/src/billing/abstractions/tax.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/tax.service.abstraction.ts @@ -1,7 +1,9 @@ import { CountryListItem } from "../models/domain"; import { PreviewIndividualInvoiceRequest } from "../models/request/preview-individual-invoice.request"; import { PreviewOrganizationInvoiceRequest } from "../models/request/preview-organization-invoice.request"; +import { PreviewTaxAmountForOrganizationTrialRequest } from "../models/request/tax"; import { PreviewInvoiceResponse } from "../models/response/preview-invoice.response"; +import { PreviewTaxAmountResponse } from "../models/response/tax"; export abstract class TaxServiceAbstraction { abstract getCountries(): CountryListItem[]; @@ -15,4 +17,8 @@ export abstract class TaxServiceAbstraction { abstract previewOrganizationInvoice( request: PreviewOrganizationInvoiceRequest, ): Promise; + + abstract previewTaxAmountForOrganizationTrial: ( + request: PreviewTaxAmountForOrganizationTrialRequest, + ) => Promise; } diff --git a/libs/common/src/billing/models/request/tax/index.ts b/libs/common/src/billing/models/request/tax/index.ts new file mode 100644 index 00000000000..cda1930c614 --- /dev/null +++ b/libs/common/src/billing/models/request/tax/index.ts @@ -0,0 +1 @@ +export * from "./preview-tax-amount-for-organization-trial.request"; diff --git a/libs/common/src/billing/models/request/tax/preview-tax-amount-for-organization-trial.request.ts b/libs/common/src/billing/models/request/tax/preview-tax-amount-for-organization-trial.request.ts new file mode 100644 index 00000000000..3f366335a47 --- /dev/null +++ b/libs/common/src/billing/models/request/tax/preview-tax-amount-for-organization-trial.request.ts @@ -0,0 +1,11 @@ +import { PlanType, ProductType } from "../../../enums"; + +export type PreviewTaxAmountForOrganizationTrialRequest = { + planType: PlanType; + productType: ProductType; + taxInformation: { + country: string; + postalCode: string; + taxId?: string; + }; +}; diff --git a/libs/common/src/billing/models/response/tax/index.ts b/libs/common/src/billing/models/response/tax/index.ts new file mode 100644 index 00000000000..525d6d7c80a --- /dev/null +++ b/libs/common/src/billing/models/response/tax/index.ts @@ -0,0 +1 @@ +export * from "./preview-tax-amount.response"; diff --git a/libs/common/src/billing/models/response/tax/preview-tax-amount.response.ts b/libs/common/src/billing/models/response/tax/preview-tax-amount.response.ts new file mode 100644 index 00000000000..cf15156551a --- /dev/null +++ b/libs/common/src/billing/models/response/tax/preview-tax-amount.response.ts @@ -0,0 +1,11 @@ +import { BaseResponse } from "../../../../models/response/base.response"; + +export class PreviewTaxAmountResponse extends BaseResponse { + taxAmount: number; + + constructor(response: any) { + super(response); + + this.taxAmount = this.getResponseProperty("TaxAmount"); + } +} diff --git a/libs/common/src/billing/services/tax.service.ts b/libs/common/src/billing/services/tax.service.ts index aa27c99adc8..2632ca7083b 100644 --- a/libs/common/src/billing/services/tax.service.ts +++ b/libs/common/src/billing/services/tax.service.ts @@ -1,3 +1,6 @@ +import { PreviewTaxAmountForOrganizationTrialRequest } from "@bitwarden/common/billing/models/request/tax"; +import { PreviewTaxAmountResponse } from "@bitwarden/common/billing/models/response/tax"; + import { ApiService } from "../../abstractions/api.service"; import { TaxServiceAbstraction } from "../abstractions/tax.service.abstraction"; import { CountryListItem } from "../models/domain"; @@ -300,4 +303,16 @@ export class TaxService implements TaxServiceAbstraction { ); return new PreviewInvoiceResponse(response); } + + async previewTaxAmountForOrganizationTrial( + request: PreviewTaxAmountForOrganizationTrialRequest, + ): Promise { + return await this.apiService.send( + "POST", + "/tax/preview-amount/organization-trial", + request, + true, + true, + ); + } } From 992b1456a82ef5626501bbdf35277d3b8078a4db Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Tue, 13 May 2025 09:31:47 -0400 Subject: [PATCH 033/245] Send SkipTrial = true to organization create when trial length is 0 (#14701) --- .../accounts/trial-initiation/trial-billing-step.component.ts | 1 + .../admin-console/models/request/organization-create.request.ts | 1 + .../src/billing/abstractions/organization-billing.service.ts | 1 + libs/common/src/billing/services/organization-billing.service.ts | 1 + 4 files changed, 4 insertions(+) diff --git a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts index 614d8bf5f97..9f910ad9d23 100644 --- a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts +++ b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts @@ -215,6 +215,7 @@ export class TrialBillingStepComponent implements OnInit, OnDestroy { const payment: PaymentInformation = { paymentMethod, billing: this.getBillingInformationFromTaxInfoComponent(), + skipTrial: this.trialLength === 0, }; const response = await this.organizationBillingService.purchaseSubscription({ diff --git a/libs/common/src/admin-console/models/request/organization-create.request.ts b/libs/common/src/admin-console/models/request/organization-create.request.ts index e8561307b20..d9c62f1e20a 100644 --- a/libs/common/src/admin-console/models/request/organization-create.request.ts +++ b/libs/common/src/admin-console/models/request/organization-create.request.ts @@ -6,4 +6,5 @@ import { OrganizationNoPaymentMethodCreateRequest } from "../../../billing/model export class OrganizationCreateRequest extends OrganizationNoPaymentMethodCreateRequest { paymentMethodType: PaymentMethodType; paymentToken: string; + skipTrial?: boolean; } diff --git a/libs/common/src/billing/abstractions/organization-billing.service.ts b/libs/common/src/billing/abstractions/organization-billing.service.ts index 8024a120b0a..58c537c99cc 100644 --- a/libs/common/src/billing/abstractions/organization-billing.service.ts +++ b/libs/common/src/billing/abstractions/organization-billing.service.ts @@ -39,6 +39,7 @@ export type BillingInformation = { export type PaymentInformation = { paymentMethod: [string, PaymentMethodType]; billing: BillingInformation; + skipTrial?: boolean; }; export type SubscriptionInformation = { diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts index c6bd88d8dd6..fe5623fd5e6 100644 --- a/libs/common/src/billing/services/organization-billing.service.ts +++ b/libs/common/src/billing/services/organization-billing.service.ts @@ -174,6 +174,7 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs const [paymentToken, paymentMethodType] = information.paymentMethod; request.paymentToken = paymentToken; request.paymentMethodType = paymentMethodType; + request.skipTrial = information.skipTrial; const billingInformation = information.billing; request.billingAddressPostalCode = billingInformation.postalCode; From b2c118d607c7be1298b39cd6c3ea3012411e5a78 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 13 May 2025 15:41:57 +0200 Subject: [PATCH 034/245] Move admin-console code to new encrypt service interface (#14547) --- .../organization-user-reset-password.service.ts | 2 +- .../providers/services/web-provider.service.ts | 2 +- .../services/default-collection-admin.service.ts | 4 ++-- .../collections/services/default-collection.service.spec.ts | 5 ++++- .../collections/services/default-collection.service.ts | 2 +- .../services/default-vnext-collection.service.spec.ts | 6 ++++++ .../services/default-vnext-collection.service.ts | 2 +- .../models/domain/encrypted-organization-key.ts | 4 ++-- 8 files changed, 18 insertions(+), 9 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts index 78d2d8fd165..ecf4d26eb52 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts @@ -112,7 +112,7 @@ export class OrganizationUserResetPasswordService if (orgSymKey == null) { throw new Error("No org key found"); } - const decPrivateKey = await this.encryptService.decryptToBytes( + const decPrivateKey = await this.encryptService.unwrapDecapsulationKey( new EncString(response.encryptedPrivateKey), orgSymKey, ); diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts index 844c6b779a9..418b7020ff9 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts @@ -74,7 +74,7 @@ export class WebProviderService { const [publicKey, encryptedPrivateKey] = await this.keyService.makeKeyPair(organizationKey); - const encryptedCollectionName = await this.encryptService.encrypt( + const encryptedCollectionName = await this.encryptService.encryptString( this.i18nService.t("defaultCollection"), organizationKey, ); diff --git a/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts b/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts index 890353d9039..293090ce315 100644 --- a/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts +++ b/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts @@ -116,7 +116,7 @@ export class DefaultCollectionAdminService implements CollectionAdminService { const promises = collections.map(async (c) => { const view = new CollectionAdminView(); view.id = c.id; - view.name = await this.encryptService.decryptToUtf8(new EncString(c.name), orgKey); + view.name = await this.encryptService.decryptString(new EncString(c.name), orgKey); view.externalId = c.externalId; view.organizationId = c.organizationId; @@ -146,7 +146,7 @@ export class DefaultCollectionAdminService implements CollectionAdminService { } const collection = new CollectionRequest(); collection.externalId = model.externalId; - collection.name = (await this.encryptService.encrypt(model.name, key)).encryptedString; + collection.name = (await this.encryptService.encryptString(model.name, key)).encryptedString; collection.groups = model.groups.map( (group) => new SelectionReadOnlyRequest(group.id, group.readOnly, group.hidePasswords, group.manage), diff --git a/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts b/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts index 7fe81ade4d2..c5f57f38dd3 100644 --- a/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts +++ b/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts @@ -120,9 +120,12 @@ const mockStateProvider = () => { const mockCryptoService = () => { const keyService = mock(); const encryptService = mock(); - encryptService.decryptToUtf8 + encryptService.decryptString .calledWith(expect.any(EncString), expect.anything()) .mockResolvedValue("DECRYPTED_STRING"); + encryptService.decryptToUtf8 + .calledWith(expect.any(EncString), expect.anything(), expect.anything()) + .mockResolvedValue("DECRYPTED_STRING"); (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); diff --git a/libs/admin-console/src/common/collections/services/default-collection.service.ts b/libs/admin-console/src/common/collections/services/default-collection.service.ts index 1ae58d3eef3..a1dd0419e2c 100644 --- a/libs/admin-console/src/common/collections/services/default-collection.service.ts +++ b/libs/admin-console/src/common/collections/services/default-collection.service.ts @@ -113,7 +113,7 @@ export class DefaultCollectionService implements CollectionService { collection.organizationId = model.organizationId; collection.readOnly = model.readOnly; collection.externalId = model.externalId; - collection.name = await this.encryptService.encrypt(model.name, key); + collection.name = await this.encryptService.encryptString(model.name, key); return collection; } diff --git a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts index 9700fcb695a..d4bc026b5bd 100644 --- a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts +++ b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts @@ -46,6 +46,11 @@ describe("DefaultvNextCollectionService", () => { keyService.orgKeys$.mockReturnValue(cryptoKeys); // Set up mock decryption + encryptService.decryptString + .calledWith(expect.any(EncString), expect.any(SymmetricCryptoKey)) + .mockImplementation((encString, key) => + Promise.resolve(encString.data.replace("ENC_", "DEC_")), + ); encryptService.decryptToUtf8 .calledWith(expect.any(EncString), expect.any(SymmetricCryptoKey), expect.any(String)) .mockImplementation((encString, key) => @@ -103,6 +108,7 @@ describe("DefaultvNextCollectionService", () => { ]); // Assert that the correct org keys were used for each encrypted string + // This should be replaced with decryptString when the platform PR (https://github.com/bitwarden/clients/pull/14544) is merged expect(encryptService.decryptToUtf8).toHaveBeenCalledWith( expect.objectContaining(new EncString(collection1.name)), orgKey1, diff --git a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts index 0ef8ae99ab3..4dcda795afe 100644 --- a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts +++ b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts @@ -113,7 +113,7 @@ export class DefaultvNextCollectionService implements vNextCollectionService { collection.organizationId = model.organizationId; collection.readOnly = model.readOnly; collection.externalId = model.externalId; - collection.name = await this.encryptService.encrypt(model.name, key); + collection.name = await this.encryptService.encryptString(model.name, key); return collection; } diff --git a/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts b/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts index 984d80ba519..297bcf08d8c 100644 --- a/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts +++ b/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts @@ -56,14 +56,14 @@ export class ProviderEncryptedOrganizationKey implements BaseEncryptedOrganizati ) {} async decrypt(encryptService: EncryptService, providerKeys: Record) { - const decValue = await encryptService.decryptToBytes( + const decValue = await encryptService.unwrapSymmetricKey( new EncString(this.key), providerKeys[this.providerId], ); if (decValue == null) { throw new Error("Failed to decrypt organization key"); } - return new SymmetricCryptoKey(decValue) as OrgKey; + return decValue as OrgKey; } get encryptedOrganizationKey() { From 00beef617caf0bc3aa81efdc5d4f34e53c1f2e08 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 13 May 2025 15:42:48 +0200 Subject: [PATCH 035/245] [PM-21586] Return null in decryptUserKeyWithMasterKey if decrypt fails (#14756) * Return null in decryptUserKeyWithMasterKey if decrypt fails * Show error on invalid master password * Add logs --- .../master-password.service.abstraction.ts | 4 ++-- .../services/master-password.service.ts | 20 ++++++++++++++----- .../src/lock/components/lock.component.ts | 9 +++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts b/libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts index 221ce8ed6ef..fded0cea023 100644 --- a/libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts +++ b/libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts @@ -37,13 +37,13 @@ export abstract class MasterPasswordServiceAbstraction { * @param userKey The user's encrypted symmetric key * @throws If either the MasterKey or UserKey are not resolved, or if the UserKey encryption type * is neither AesCbc256_B64 nor AesCbc256_HmacSha256_B64 - * @returns The user key + * @returns The user key or null if the masterkey is wrong */ abstract decryptUserKeyWithMasterKey: ( masterKey: MasterKey, userId: string, userKey?: EncString, - ) => Promise; + ) => Promise; } export abstract class InternalMasterPasswordServiceAbstraction extends MasterPasswordServiceAbstraction { diff --git a/libs/common/src/key-management/master-password/services/master-password.service.ts b/libs/common/src/key-management/master-password/services/master-password.service.ts index b9b11d6cbe8..9e58680d453 100644 --- a/libs/common/src/key-management/master-password/services/master-password.service.ts +++ b/libs/common/src/key-management/master-password/services/master-password.service.ts @@ -166,7 +166,7 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr masterKey: MasterKey, userId: UserId, userKey?: EncString, - ): Promise { + ): Promise { userKey ??= await this.getMasterKeyEncryptedUserKey(userId); masterKey ??= await firstValueFrom(this.masterKey$(userId)); @@ -177,16 +177,26 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr let decUserKey: SymmetricCryptoKey; if (userKey.encryptionType === EncryptionType.AesCbc256_B64) { - decUserKey = await this.encryptService.unwrapSymmetricKey(userKey, masterKey); + try { + decUserKey = await this.encryptService.unwrapSymmetricKey(userKey, masterKey); + } catch { + this.logService.warning("Failed to decrypt user key with master key."); + return null; + } } else if (userKey.encryptionType === EncryptionType.AesCbc256_HmacSha256_B64) { - const newKey = await this.keyGenerationService.stretchKey(masterKey); - decUserKey = await this.encryptService.unwrapSymmetricKey(userKey, newKey); + try { + const newKey = await this.keyGenerationService.stretchKey(masterKey); + decUserKey = await this.encryptService.unwrapSymmetricKey(userKey, newKey); + } catch { + this.logService.warning("Failed to decrypt user key with stretched master key."); + return null; + } } else { throw new Error("Unsupported encryption type."); } if (decUserKey == null) { - this.logService.warning("Failed to decrypt user key with master key."); + this.logService.warning("Failed to decrypt user key with master key, user key is null."); return null; } diff --git a/libs/key-management-ui/src/lock/components/lock.component.ts b/libs/key-management-ui/src/lock/components/lock.component.ts index 80d64e17b84..3cb0dbaca52 100644 --- a/libs/key-management-ui/src/lock/components/lock.component.ts +++ b/libs/key-management-ui/src/lock/components/lock.component.ts @@ -556,6 +556,15 @@ export class LockComponent implements OnInit, OnDestroy { masterPasswordVerificationResponse!.masterKey, this.activeAccount.id, ); + if (userKey == null) { + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("invalidMasterPassword"), + }); + return; + } + await this.setUserKeyAndContinue(userKey, true); } From 9f3310ed7edecb2afc68a284dedd37f68dae72f9 Mon Sep 17 00:00:00 2001 From: Tom <144813356+ttalty@users.noreply.github.com> Date: Tue, 13 May 2025 09:44:54 -0400 Subject: [PATCH 036/245] Redoing the PR for get by domain due to file move (#14746) --- .../services/risk-insights-report.service.spec.ts | 10 +++++----- .../services/risk-insights-report.service.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts index 705eb1231a9..f9177bf1bf7 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts @@ -50,7 +50,7 @@ describe("RiskInsightsReportService", () => { let testCase = testCaseResults[0]; expect(testCase).toBeTruthy(); expect(testCase.cipherMembers).toHaveLength(2); - expect(testCase.trimmedUris).toHaveLength(3); + expect(testCase.trimmedUris).toHaveLength(2); expect(testCase.weakPasswordDetail).toBeTruthy(); expect(testCase.exposedPasswordDetail).toBeTruthy(); expect(testCase.reusedPasswordCount).toEqual(2); @@ -69,7 +69,7 @@ describe("RiskInsightsReportService", () => { it("should generate the raw data + uri report correctly", async () => { const result = await firstValueFrom(service.generateRawDataUriReport$("orgId")); - expect(result).toHaveLength(9); + expect(result).toHaveLength(8); // Two ciphers that have google.com as their uri. There should be 2 results const googleResults = result.filter((x) => x.trimmedUri === "google.com"); @@ -88,7 +88,7 @@ describe("RiskInsightsReportService", () => { it("should generate applications health report data correctly", async () => { const result = await firstValueFrom(service.generateApplicationsReport$("orgId")); - expect(result).toHaveLength(6); + expect(result).toHaveLength(5); // Two ciphers have google.com associated with them. The first cipher // has 2 members and the second has 4. However, the 2 members in the first @@ -132,7 +132,7 @@ describe("RiskInsightsReportService", () => { expect(reportSummary.totalMemberCount).toEqual(7); expect(reportSummary.totalAtRiskMemberCount).toEqual(6); - expect(reportSummary.totalApplicationCount).toEqual(6); - expect(reportSummary.totalAtRiskApplicationCount).toEqual(5); + expect(reportSummary.totalApplicationCount).toEqual(5); + expect(reportSummary.totalAtRiskApplicationCount).toEqual(4); }); }); diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts index 027760f678c..8a6eb5000cd 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts @@ -428,7 +428,7 @@ export class RiskInsightsReportService { const cipherUris: string[] = []; const uris = cipher.login?.uris ?? []; uris.map((u: { uri: string }) => { - const uri = Utils.getHostname(u.uri).replace("www.", ""); + const uri = Utils.getDomain(u.uri); if (!cipherUris.includes(uri)) { cipherUris.push(uri); } From 0b0397c3f05d94e1ea8cbedd97b7706fba968e9b Mon Sep 17 00:00:00 2001 From: Patrick-Pimentel-Bitwarden Date: Tue, 13 May 2025 10:07:38 -0400 Subject: [PATCH 037/245] fix(enums-eslint): Enum Rule for ESLint (#14650) * fix(enums-eslint): Enum Rule for ESLint - Added enums in the warnings for eslint. * fix(enums-eslint): Enum Rule for ESLint - Updated to error in both places for enums. * fix(enums-eslint): Enum Rule for ESLint - Added new eslint plugin for warning on enums. * fix(enums-eslint): Enum Rule for ESLint - Changed based on suggestion. Co-authored-by: Andreas Coroiu * refactor(browser-platform-utils): Remove Deprecation and Fix Code - Changed usages of firefox to private and moved the usages to the preferred public method and removed the deprecations. * fix(enums-eslint): Enum Rule for ESLint - Updated to error and added disable rules for all other places. * fix(enums-eslint): Enum Rule for ESLint - Undid other changes by accident --- .../autofill/enums/autofill-overlay.enum.ts | 2 + .../fido2/content/messaging/message.ts | 2 + .../tools/popup/send-v2/send-v2.component.ts | 2 + .../at-risk-carousel-dialog.component.ts | 2 + .../vault-generator-dialog.component.ts | 2 + .../components/vault-v2/vault-v2.component.ts | 2 + .../src/ipc.service.ts | 2 + .../src/app/tools/send/send.component.ts | 2 + .../src/autofill/models/ssh-agent-setting.ts | 2 + apps/desktop/src/types/biometric-message.ts | 2 + .../credential-generator-dialog.component.ts | 2 + .../bulk-collections-dialog.component.ts | 2 + .../collections/vault.component.ts | 2 + .../manage/group-add-edit.component.ts | 4 ++ .../member-dialog/member-dialog.component.ts | 4 ++ .../components/reset-password.component.ts | 2 + .../policies/policy-edit.component.ts | 2 + .../delete-organization-dialog.component.ts | 2 + .../access-selector.component.ts | 2 + .../access-selector/access-selector.models.ts | 4 ++ .../collection-dialog.component.ts | 6 ++ ...bauthn-login-credential-prf-status.enum.ts | 2 + .../enums/emergency-access-status-type.ts | 2 + .../enums/emergency-access-type.ts | 2 + .../emergency-access-confirm.component.ts | 2 + .../emergency-access-add-edit.component.ts | 2 + .../emergency-access-takeover.component.ts | 2 + .../create-credential-dialog.component.ts | 2 + .../trial-billing-step.component.ts | 4 ++ .../change-plan-dialog.component.ts | 4 ++ .../download-license.component.ts | 2 + ...ization-subscription-selfhost.component.ts | 2 + .../shared/add-credit-dialog.component.ts | 2 + .../adjust-payment-dialog.component.ts | 2 + .../adjust-storage-dialog.component.ts | 2 + .../shared/offboarding-survey.component.ts | 2 + .../billing/shared/update-license-types.ts | 2 + apps/web/src/app/dirt/reports/reports.ts | 2 + .../reports/shared/models/report-variant.ts | 2 + .../vault-item-dialog.component.ts | 2 + .../web-generator-dialog.component.ts | 2 + .../individual-vault/add-edit-v2.component.ts | 2 + .../bulk-delete-dialog.component.ts | 2 + .../bulk-move-dialog.component.ts | 2 + .../folder-add-edit.component.ts | 2 + .../services/vault-banners.service.ts | 2 + .../models/vault-filter-section.type.ts | 2 + .../vault/individual-vault/view.component.ts | 2 + .../browser-extension-prompt.service.ts | 2 + .../risk-insights/models/password-health.ts | 2 + .../add-edit-member-dialog.component.ts | 2 + ...-existing-organization-dialog.component.ts | 2 + .../clients/create-client-dialog.component.ts | 2 + .../manage-client-name-dialog.component.ts | 2 + ...ge-client-subscription-dialog.component.ts | 2 + .../risk-insights.component.ts | 2 + .../dialog/project-dialog.component.ts | 2 + .../secrets/dialog/secret-dialog.component.ts | 4 ++ .../service-account-dialog.component.ts | 2 + .../models/enums/ap-item.enum.ts | 2 + .../models/enums/ap-permission.enum.ts | 2 + .../bulk-confirmation-dialog.component.ts | 2 + eslint.config.mjs | 1 + .../add-account-credit-dialog.component.ts | 2 + .../src/tools/send/add-edit.component.ts | 2 + .../input-password.component.ts | 2 + .../login-decryption-options.component.ts | 2 + .../login-via-auth-request.component.ts | 2 + .../auth/src/angular/login/login.component.ts | 2 + .../registration-start.component.ts | 2 + .../two-factor-auth-component.service.ts | 4 ++ .../active-client-verification-option.enum.ts | 2 + .../validators/compare-inputs.validator.ts | 2 + .../enums/organization-api-key-type.enum.ts | 2 + .../organization-connection-type.enum.ts | 2 + .../organization-user-status-type.enum.ts | 2 + .../enums/organization-user-type.enum.ts | 2 + .../admin-console/enums/policy-type.enum.ts | 2 + .../enums/provider-status-type.enum.ts | 2 + .../admin-console/enums/provider-type.enum.ts | 2 + .../enums/provider-user-status-type.enum.ts | 2 + .../enums/provider-user-type.enum.ts | 2 + .../enums/scim-provider-type.enum.ts | 2 + .../src/auth/enums/auth-request-type.ts | 2 + .../src/auth/enums/authentication-status.ts | 2 + .../src/auth/enums/authentication-type.ts | 2 + libs/common/src/auth/enums/sso.ts | 12 ++++ .../auth/enums/two-factor-provider-type.ts | 2 + .../src/auth/enums/verification-type.ts | 2 + .../domain/force-set-password-reason.ts | 2 + .../common/src/auth/services/token.service.ts | 2 + .../enums/bitwarden-product-type.enum.ts | 2 + .../billing/enums/payment-method-type.enum.ts | 2 + .../src/billing/enums/plan-interval.enum.ts | 2 + .../enums/plan-sponsorship-type.enum.ts | 2 + .../src/billing/enums/plan-type.enum.ts | 2 + .../billing/enums/product-tier-type.enum.ts | 2 + .../src/billing/enums/product-type.enum.ts | 2 + .../billing/enums/transaction-type.enum.ts | 2 + libs/common/src/enums/client-type.enum.ts | 2 + libs/common/src/enums/device-type.enum.ts | 2 + .../src/enums/event-system-user.enum.ts | 2 + libs/common/src/enums/event-type.enum.ts | 2 + libs/common/src/enums/feature-flag.enum.ts | 2 + .../common/src/enums/http-status-code.enum.ts | 2 + .../common/src/enums/integration-type.enum.ts | 2 + .../enums/native-messaging-version.enum.ts | 2 + .../src/enums/notification-type.enum.ts | 2 + libs/common/src/enums/push-technology.enum.ts | 2 + .../enums/vault-timeout-action.enum.ts | 2 + .../abstractions/environment.service.ts | 2 + ...fido2-authenticator.service.abstraction.ts | 4 ++ .../platform/enums/encryption-type.enum.ts | 2 + .../platform/enums/file-upload-type.enum.ts | 2 + .../src/platform/enums/hash-purpose.enum.ts | 2 + .../enums/html-storage-location.enum.ts | 2 + .../platform/enums/key-suffix-options.enum.ts | 2 + .../src/platform/enums/log-level-type.enum.ts | 2 + .../platform/enums/storage-location.enum.ts | 2 + .../src/platform/enums/theme-type.enum.ts | 2 + .../services/cryptography/initializer-key.ts | 2 + ...8-move-provider-state-to-state-provider.ts | 4 ++ .../30-move-policy-state-to-state-provider.ts | 2 + ...ve-organization-state-to-state-provider.ts | 8 +++ .../migrations/54-move-encrypted-sends.ts | 2 + .../59-move-kdf-config-to-state-provider.ts | 2 + ...-timeout-settings-svc-to-state-provider.ts | 2 + libs/common/src/tools/send/enums/send-type.ts | 2 + .../src/vault/enums/cipher-reprompt-type.ts | 2 + libs/common/src/vault/enums/cipher-type.ts | 2 + .../common/src/vault/enums/field-type.enum.ts | 2 + .../src/vault/enums/linked-id-type.enum.ts | 6 ++ .../src/vault/enums/secure-note-type.enum.ts | 2 + .../tasks/enums/security-task-status.enum.ts | 2 + .../tasks/enums/security-task-type.enum.ts | 2 + .../color-password.component.ts | 2 + libs/eslint/platform/index.mjs | 3 +- libs/eslint/platform/no-enums.mjs | 23 ++++++ libs/eslint/platform/no-enums.spec.mjs | 71 +++++++++++++++++++ .../importers/fsecure/fsecure-fsk-types.ts | 2 + .../lastpass/access/enums/idp-provider.ts | 2 + .../access/enums/lastpass-login-type.ts | 2 + .../lastpass/access/enums/otp-method.ts | 2 + .../lastpass/access/enums/platform.ts | 2 + .../types/onepassword-1pux-importer-types.ts | 4 ++ .../protonpass/types/protonpass-json-type.ts | 2 + .../src/biometrics/biometrics-commands.ts | 2 + .../src/biometrics/biometrics-status.ts | 2 + .../key-management/src/enums/kdf-type.enum.ts | 2 + .../src/enums/encrypted-export-type.enum.ts | 2 + .../send-add-edit-dialog.component.ts | 2 + .../send-details/send-details.component.ts | 2 + .../attachments/attachments-v2.component.ts | 2 + .../add-edit-folder-dialog.component.ts | 2 + .../assign-collections.component.ts | 2 + .../src/services/vault-nudges.service.ts | 2 + 156 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 libs/eslint/platform/no-enums.mjs create mode 100644 libs/eslint/platform/no-enums.spec.mjs diff --git a/apps/browser/src/autofill/enums/autofill-overlay.enum.ts b/apps/browser/src/autofill/enums/autofill-overlay.enum.ts index 9cc457f3c1a..d0b970671a8 100644 --- a/apps/browser/src/autofill/enums/autofill-overlay.enum.ts +++ b/apps/browser/src/autofill/enums/autofill-overlay.enum.ts @@ -21,6 +21,8 @@ export const RedirectFocusDirection = { Next: "next", } as const; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum InlineMenuFillType { AccountCreationUsername = 5, PasswordGeneration = 6, diff --git a/apps/browser/src/autofill/fido2/content/messaging/message.ts b/apps/browser/src/autofill/fido2/content/messaging/message.ts index 5815be9eb60..640af22ab9a 100644 --- a/apps/browser/src/autofill/fido2/content/messaging/message.ts +++ b/apps/browser/src/autofill/fido2/content/messaging/message.ts @@ -5,6 +5,8 @@ import { AssertCredentialResult, } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum MessageType { CredentialCreationRequest, CredentialCreationResponse, diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts index 49804abda5d..def425a51a5 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts @@ -26,6 +26,8 @@ import { PopOutComponent } from "../../../platform/popup/components/pop-out.comp import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum SendState { Empty, NoResults, diff --git a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts index fcca125c2b6..0133bccd25c 100644 --- a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts +++ b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts @@ -10,6 +10,8 @@ import { import { I18nPipe } from "@bitwarden/ui-common"; import { DarkImageSourceDirective, VaultCarouselModule } from "@bitwarden/vault"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AtRiskCarouselDialogResult { Dismissed = "dismissed", } diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts index 137f2a9dac3..4daffa6a9b8 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts @@ -30,6 +30,8 @@ export interface GeneratorDialogResult { generatedValue?: string; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum GeneratorDialogAction { Selected = "selected", Canceled = "canceled", diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 64805a02394..4a8625f982c 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -55,6 +55,8 @@ import { VaultPageService } from "./vault-page.service"; import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from "."; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum VaultState { Empty, NoResults, diff --git a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts index b02ff1a4225..d8616e9757a 100644 --- a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts @@ -15,6 +15,8 @@ const DEFAULT_MESSAGE_TIMEOUT = 10 * 1000; // 10 seconds export type MessageHandler = (MessageCommon) => void; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum IPCConnectionState { Disconnected = "disconnected", Connecting = "connecting", diff --git a/apps/desktop/src/app/tools/send/send.component.ts b/apps/desktop/src/app/tools/send/send.component.ts index cc3007ae133..6c2c3ed53c6 100644 --- a/apps/desktop/src/app/tools/send/send.component.ts +++ b/apps/desktop/src/app/tools/send/send.component.ts @@ -25,6 +25,8 @@ import { SearchBarService } from "../../layout/search/search-bar.service"; import { AddEditComponent } from "./add-edit.component"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum Action { None = "", Add = "add", diff --git a/apps/desktop/src/autofill/models/ssh-agent-setting.ts b/apps/desktop/src/autofill/models/ssh-agent-setting.ts index f332cc93ee1..1775cf35588 100644 --- a/apps/desktop/src/autofill/models/ssh-agent-setting.ts +++ b/apps/desktop/src/autofill/models/ssh-agent-setting.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum SshAgentPromptType { Always = "always", Never = "never", diff --git a/apps/desktop/src/types/biometric-message.ts b/apps/desktop/src/types/biometric-message.ts index f7a7ef0c507..7616b265005 100644 --- a/apps/desktop/src/types/biometric-message.ts +++ b/apps/desktop/src/types/biometric-message.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum BiometricAction { Authenticate = "authenticate", GetStatus = "status", diff --git a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts index 2858d7330e5..204615443ba 100644 --- a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts +++ b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts @@ -31,6 +31,8 @@ export interface CredentialGeneratorDialogResult { generatedValue?: string; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CredentialGeneratorDialogAction { Selected = "selected", Canceled = "canceled", diff --git a/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts b/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts index dd19c66f21e..147340e6a00 100644 --- a/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts @@ -43,6 +43,8 @@ export interface BulkCollectionsDialogParams { collections: CollectionView[]; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum BulkCollectionsDialogResult { Saved = "saved", Canceled = "canceled", diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts index 687aef9b671..96c00faceb2 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts @@ -132,6 +132,8 @@ import { VaultHeaderComponent } from "./vault-header/vault-header.component"; const BroadcasterSubscriptionId = "OrgVaultComponent"; const SearchTextDebounceInterval = 200; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum AddAccessStatusType { All = 0, AddAccess = 1, diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts index 2a5af32ecc2..f29b4b642cb 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts @@ -58,6 +58,8 @@ import { AddEditGroupDetail } from "./../core/views/add-edit-group-detail"; /** * Indices for the available tabs in the dialog */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum GroupAddEditTabType { Info = 0, Members = 1, @@ -82,6 +84,8 @@ export interface GroupAddEditDialogParams { initialTab?: GroupAddEditTabType; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum GroupAddEditDialogResultType { Saved = "saved", Canceled = "canceled", diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index c90a2a657e7..8349b44c735 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -64,6 +64,8 @@ import { commaSeparatedEmails } from "./validators/comma-separated-emails.valida import { inputEmailLimitValidator } from "./validators/input-email-limit.validator"; import { orgSeatLimitReachedValidator } from "./validators/org-seat-limit-reached.validator"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum MemberDialogTab { Role = 0, Groups = 1, @@ -92,6 +94,8 @@ export interface EditMemberDialogParams extends CommonMemberDialogParams { export type MemberDialogParams = EditMemberDialogParams | AddMemberDialogParams; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum MemberDialogResult { Saved = "saved", Canceled = "canceled", diff --git a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts index f08cb0b7d7c..4e78d4dc91f 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts @@ -50,6 +50,8 @@ export type ResetPasswordDialogData = { organizationId: string; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ResetPasswordDialogResult { Ok = "ok", } diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts index 49f4d15a100..4d722840e23 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts @@ -41,6 +41,8 @@ export type PolicyEditDialogData = { organizationId: string; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum PolicyEditDialogResult { Saved = "saved", UpgradePlan = "upgrade-plan", diff --git a/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts b/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts index c23dcf2c8f2..e942eecbd37 100644 --- a/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts @@ -71,6 +71,8 @@ export interface DeleteOrganizationDialogParams { requestType: "InvalidFamiliesForEnterprise" | "RegularDelete"; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum DeleteOrganizationDialogResult { Deleted = "deleted", Canceled = "canceled", diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts index edd0bfcaada..1db1fc8a06e 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts @@ -26,6 +26,8 @@ import { Permission, } from "./access-selector.models"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum PermissionMode { /** * No permission controls or column present. No permission values are emitted. diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts index 8702c0f7a6c..884483d32b0 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts @@ -15,6 +15,8 @@ import { GroupView } from "../../../core"; /** * Permission options that replace/correspond with manage, readOnly, and hidePassword server fields. */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CollectionPermission { View = "view", ViewExceptPass = "viewExceptPass", @@ -23,6 +25,8 @@ export enum CollectionPermission { Manage = "manage", } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AccessItemType { Collection, Group, diff --git a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts index 37d0ebbd195..07bff3aba64 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts @@ -65,6 +65,8 @@ import { } from "../access-selector/access-selector.models"; import { AccessSelectorModule } from "../access-selector/access-selector.module"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CollectionDialogTabType { Info = 0, Access = 1, @@ -76,6 +78,8 @@ export enum CollectionDialogTabType { * @readonly * @enum {string} */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum ButtonType { /** Displayed when the user has reached the maximum number of collections allowed for the organization. */ Upgrade = "upgrade", @@ -103,6 +107,8 @@ export interface CollectionDialogResult { collection: CollectionResponse | CollectionView; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CollectionDialogAction { Saved = "saved", Canceled = "canceled", diff --git a/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts b/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts index 7dc8217fde5..3073917e57b 100644 --- a/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts +++ b/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum WebauthnLoginCredentialPrfStatus { Enabled = 0, Supported = 1, diff --git a/apps/web/src/app/auth/emergency-access/enums/emergency-access-status-type.ts b/apps/web/src/app/auth/emergency-access/enums/emergency-access-status-type.ts index 94400f34e6e..16aa2546101 100644 --- a/apps/web/src/app/auth/emergency-access/enums/emergency-access-status-type.ts +++ b/apps/web/src/app/auth/emergency-access/enums/emergency-access-status-type.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EmergencyAccessStatusType { Invited = 0, Accepted = 1, diff --git a/apps/web/src/app/auth/emergency-access/enums/emergency-access-type.ts b/apps/web/src/app/auth/emergency-access/enums/emergency-access-type.ts index 61a366c433e..ecb0c5a3d07 100644 --- a/apps/web/src/app/auth/emergency-access/enums/emergency-access-type.ts +++ b/apps/web/src/app/auth/emergency-access/enums/emergency-access-type.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EmergencyAccessType { View = 0, Takeover = 1, diff --git a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts index f3fd19a4e8b..95afc167374 100644 --- a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts @@ -8,6 +8,8 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { DialogConfig, DialogRef, DIALOG_DATA, DialogService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EmergencyAccessConfirmDialogResult { Confirmed = "confirmed", } diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts index cf52969c244..1a6510ef011 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts @@ -26,6 +26,8 @@ export type EmergencyAccessAddEditDialogData = { readOnly: boolean; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EmergencyAccessAddEditDialogResult { Saved = "saved", Canceled = "canceled", diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts index c80f82ae126..edb85dc0f1a 100644 --- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts @@ -24,6 +24,8 @@ import { KdfType, KdfConfigService, KeyService } from "@bitwarden/key-management import { EmergencyAccessService } from "../../../emergency-access"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EmergencyAccessTakeoverResultType { Done = "done", } diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts index 591fe3816dc..8e7e25896ab 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts @@ -19,6 +19,8 @@ import { PendingWebauthnLoginCredentialView } from "../../../core/views/pending- import { CreatePasskeyFailedIcon } from "./create-passkey-failed.icon"; import { CreatePasskeyIcon } from "./create-passkey.icon"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CreateCredentialDialogResult { Success, } diff --git a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts index 9f910ad9d23..63c42139648 100644 --- a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts +++ b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts @@ -50,11 +50,15 @@ export interface OrganizationCreatedEvent { planDescription: string; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum SubscriptionCadence { Annual, Monthly, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum SubscriptionProduct { PasswordManager, SecretsManager, diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts index 8cff90edd5b..49c5bb775b1 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts @@ -74,11 +74,15 @@ type ChangePlanDialogParams = { productTierType: ProductTierType; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ChangePlanDialogResultType { Closed = "closed", Submitted = "submitted", } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum PlanCardState { Selected = "selected", NotSelected = "not_selected", diff --git a/apps/web/src/app/billing/organizations/download-license.component.ts b/apps/web/src/app/billing/organizations/download-license.component.ts index fecb58a7a9d..66778aec50f 100644 --- a/apps/web/src/app/billing/organizations/download-license.component.ts +++ b/apps/web/src/app/billing/organizations/download-license.component.ts @@ -7,6 +7,8 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { DialogConfig, DIALOG_DATA, DialogRef, DialogService } from "@bitwarden/components"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum DownloadLicenseDialogResult { Cancelled = "cancelled", Downloaded = "downloaded", diff --git a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts index e6854a5216b..2189bfa830f 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts @@ -27,6 +27,8 @@ import { DialogService, ToastService } from "@bitwarden/components"; import { BillingSyncKeyComponent } from "./billing-sync-key.component"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum LicenseOptions { SYNC = 0, UPLOAD = 1, diff --git a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts b/apps/web/src/app/billing/shared/add-credit-dialog.component.ts index 45dab542ce8..ec6e251418b 100644 --- a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts +++ b/apps/web/src/app/billing/shared/add-credit-dialog.component.ts @@ -21,6 +21,8 @@ export interface AddCreditDialogData { organizationId: string; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AddCreditDialogResult { Added = "added", Cancelled = "cancelled", diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts index e7b7cc78250..9d32becd1bb 100644 --- a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts +++ b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts @@ -30,6 +30,8 @@ export interface AdjustPaymentDialogParams { providerId?: string; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AdjustPaymentDialogResultType { Closed = "closed", Submitted = "submitted", diff --git a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts index 3acba414df4..6cd17218f02 100644 --- a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts +++ b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts @@ -22,6 +22,8 @@ export interface AdjustStorageDialogParams { organizationId?: string; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AdjustStorageDialogResultType { Submitted = "submitted", Closed = "closed", diff --git a/apps/web/src/app/billing/shared/offboarding-survey.component.ts b/apps/web/src/app/billing/shared/offboarding-survey.component.ts index cecbc302f40..62213c1fe94 100644 --- a/apps/web/src/app/billing/shared/offboarding-survey.component.ts +++ b/apps/web/src/app/billing/shared/offboarding-survey.component.ts @@ -25,6 +25,8 @@ type OrganizationOffboardingParams = { export type OffboardingSurveyDialogParams = UserOffboardingParams | OrganizationOffboardingParams; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum OffboardingSurveyDialogResultType { Closed = "closed", Submitted = "submitted", diff --git a/apps/web/src/app/billing/shared/update-license-types.ts b/apps/web/src/app/billing/shared/update-license-types.ts index 8f939ac62a9..8ba13541ba8 100644 --- a/apps/web/src/app/billing/shared/update-license-types.ts +++ b/apps/web/src/app/billing/shared/update-license-types.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum UpdateLicenseDialogResult { Updated = "updated", Cancelled = "cancelled", diff --git a/apps/web/src/app/dirt/reports/reports.ts b/apps/web/src/app/dirt/reports/reports.ts index 500ae23e5cf..c47928af1e9 100644 --- a/apps/web/src/app/dirt/reports/reports.ts +++ b/apps/web/src/app/dirt/reports/reports.ts @@ -7,6 +7,8 @@ import { ReportUnsecuredWebsites } from "./icons/report-unsecured-websites.icon" import { ReportWeakPasswords } from "./icons/report-weak-passwords.icon"; import { ReportEntry } from "./shared"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ReportType { ExposedPasswords = "exposedPasswords", ReusedPasswords = "reusedPasswords", diff --git a/apps/web/src/app/dirt/reports/shared/models/report-variant.ts b/apps/web/src/app/dirt/reports/shared/models/report-variant.ts index 3beba65f7d9..48b213f4cf6 100644 --- a/apps/web/src/app/dirt/reports/shared/models/report-variant.ts +++ b/apps/web/src/app/dirt/reports/shared/models/report-variant.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ReportVariant { Enabled = "Enabled", RequiresPremium = "RequiresPremium", diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index 460b8d58d63..10c35f861b9 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -95,6 +95,8 @@ export interface VaultItemDialogParams { restore?: (c: CipherView) => Promise; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum VaultItemDialogResult { /** * A cipher was saved (created or updated). diff --git a/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts b/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts index 2d0aa0231f1..e20efa9dbb8 100644 --- a/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts +++ b/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts @@ -26,6 +26,8 @@ export interface WebVaultGeneratorDialogResult { generatedValue?: string; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum WebVaultGeneratorDialogAction { Selected = "selected", Canceled = "canceled", diff --git a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts index 5dcbf0d4e78..621e0ec88c5 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts @@ -35,6 +35,8 @@ import { WebCipherFormGenerationService } from "../services/web-cipher-form-gene /** * The result of the AddEditCipherDialogV2 component. */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AddEditCipherDialogResult { Edited = "edited", Added = "added", diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts index f8084b03e33..1650b0f371f 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts @@ -29,6 +29,8 @@ export interface BulkDeleteDialogParams { unassignedCiphers?: string[]; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum BulkDeleteDialogResult { Deleted = "deleted", Canceled = "canceled", diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts index 8f0827e4b51..d287c430d49 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts @@ -23,6 +23,8 @@ export interface BulkMoveDialogParams { cipherIds?: string[]; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum BulkMoveDialogResult { Moved = "moved", Canceled = "canceled", diff --git a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts index ad6cbfad43d..6a3c5663d93 100644 --- a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts @@ -113,6 +113,8 @@ export interface FolderAddEditDialogParams { folderId: string; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum FolderAddEditDialogResult { Deleted = "deleted", Canceled = "canceled", diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts index 1fa5ae1ad8b..ca16541f88f 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts @@ -17,6 +17,8 @@ import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { PBKDF2KdfConfig, KdfConfigService, KdfType } from "@bitwarden/key-management"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum VisibleVaultBanner { KDFSettings = "kdf-settings", OutdatedBrowser = "outdated-browser", diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter-section.type.ts b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter-section.type.ts index 0f949e17146..7566dbdc507 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter-section.type.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter-section.type.ts @@ -15,6 +15,8 @@ export type VaultFilterType = | FolderFilter | CollectionFilter; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum VaultFilterLabel { OrganizationFilter = "organizationFilter", TypeFilter = "typeFilter", diff --git a/apps/web/src/app/vault/individual-vault/view.component.ts b/apps/web/src/app/vault/individual-vault/view.component.ts index e7b06cbb8d6..f52a4da3ffb 100644 --- a/apps/web/src/app/vault/individual-vault/view.component.ts +++ b/apps/web/src/app/vault/individual-vault/view.component.ts @@ -54,6 +54,8 @@ export interface ViewCipherDialogParams { disableEdit?: boolean; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ViewCipherDialogResult { Edited = "edited", Deleted = "deleted", diff --git a/apps/web/src/app/vault/services/browser-extension-prompt.service.ts b/apps/web/src/app/vault/services/browser-extension-prompt.service.ts index 5617d4aef75..f928404a2a9 100644 --- a/apps/web/src/app/vault/services/browser-extension-prompt.service.ts +++ b/apps/web/src/app/vault/services/browser-extension-prompt.service.ts @@ -7,6 +7,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { Utils } from "@bitwarden/common/platform/misc/utils"; import { VaultMessages } from "@bitwarden/common/vault/enums/vault-messages.enum"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum BrowserPromptState { Loading = "loading", Error = "error", diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts index 723d737d5bd..b8333828693 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts @@ -149,6 +149,8 @@ export interface PasswordHealthReportApplicationsRequest { url: string; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum DrawerType { None = 0, AppAtRiskMembers = 1, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts index 0213903c4d9..67f2382cf91 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts @@ -25,6 +25,8 @@ export type AddEditMemberDialogParams = { }; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AddEditMemberDialogResultType { Closed = "closed", Deleted = "deleted", diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts index 0d1602946c7..4bb2c36ef15 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts @@ -18,6 +18,8 @@ export type AddExistingOrganizationDialogParams = { provider: Provider; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AddExistingOrganizationDialogResultType { Closed = "closed", Submitted = "submitted", diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts index d3245916ad7..d71e18cd539 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts @@ -22,6 +22,8 @@ type CreateClientDialogParams = { plans: PlanResponse[]; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CreateClientDialogResultType { Closed = "closed", Submitted = "submitted", diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts index bbcd5f7ed63..45abeab1f4a 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts @@ -23,6 +23,8 @@ type ManageClientNameDialogParams = { }; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ManageClientNameDialogResultType { Closed = "closed", Submitted = "submitted", diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts index b79c887e77e..ced48bfdbea 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts @@ -18,6 +18,8 @@ type ManageClientSubscriptionDialogParams = { provider: Provider; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ManageClientSubscriptionDialogResultType { Closed = "closed", Submitted = "submitted", diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts index ab74869bfc9..5aca124a46a 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts @@ -37,6 +37,8 @@ import { PasswordHealthMembersURIComponent } from "./password-health-members-uri import { PasswordHealthMembersComponent } from "./password-health-members.component"; import { PasswordHealthComponent } from "./password-health.component"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum RiskInsightsTabType { AllApps = 0, CriticalApps = 1, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts index 74824de00e0..c96887cc9ac 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts @@ -11,6 +11,8 @@ import { DialogRef, DIALOG_DATA, BitValidators, ToastService } from "@bitwarden/ import { ProjectView } from "../../models/view/project.view"; import { ProjectService } from "../../projects/project.service"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum OperationType { Add, Edit, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts index 88b3e5e2172..09a78e02c44 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts @@ -44,11 +44,15 @@ import { SecretService } from "../secret.service"; import { SecretDeleteDialogComponent, SecretDeleteOperation } from "./secret-delete.component"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum OperationType { Add, Edit, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum SecretDialogTabType { NameValuePair = 0, People = 1, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts index e1cd7a77b28..241c736fb7a 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts @@ -10,6 +10,8 @@ import { DialogRef, DIALOG_DATA, BitValidators, ToastService } from "@bitwarden/ import { ServiceAccountView } from "../../models/view/service-account.view"; import { ServiceAccountService } from "../service-account.service"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum OperationType { Add, Edit, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-item.enum.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-item.enum.ts index 6d060ac255d..6b92fd7458a 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-item.enum.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-item.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ApItemEnum { User, Group, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-permission.enum.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-permission.enum.ts index eb442b0af5d..a57f9636178 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-permission.enum.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-permission.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ApPermissionEnum { CanRead = "canRead", CanReadWrite = "canReadWrite", diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts index a43207ed75e..935ee1c8518 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts @@ -15,6 +15,8 @@ export interface BulkConfirmationStatus { description: string; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum BulkConfirmationResult { Continue, Cancel, diff --git a/eslint.config.mjs b/eslint.config.mjs index 9d93d1118c0..7928224dc00 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -73,6 +73,7 @@ export default tseslint.config( "@angular-eslint/use-lifecycle-interface": "error", "@angular-eslint/use-pipe-transform-interface": 0, "@bitwarden/platform/required-using": "error", + "@bitwarden/platform/no-enums": "error", "@typescript-eslint/explicit-member-accessibility": ["error", { accessibility: "no-public" }], "@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled "@typescript-eslint/no-floating-promises": "error", diff --git a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts index 7c068c51d86..871895c2ede 100644 --- a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts +++ b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts @@ -22,6 +22,8 @@ export type AddAccountCreditDialogParams = { providerId?: string; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AddAccountCreditDialogResultType { Closed = "closed", Submitted = "submitted", diff --git a/libs/angular/src/tools/send/add-edit.component.ts b/libs/angular/src/tools/send/add-edit.component.ts index 7e6180e5849..0289664c365 100644 --- a/libs/angular/src/tools/send/add-edit.component.ts +++ b/libs/angular/src/tools/send/add-edit.component.ts @@ -36,6 +36,8 @@ import { SendService } from "@bitwarden/common/tools/send/services/send.service. import { DialogService, ToastService } from "@bitwarden/components"; // Value = hours +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum DatePreset { OneHour = 1, OneDay = 24, diff --git a/libs/auth/src/angular/input-password/input-password.component.ts b/libs/auth/src/angular/input-password/input-password.component.ts index bffb8a56b5e..dff4eafc3c2 100644 --- a/libs/auth/src/angular/input-password/input-password.component.ts +++ b/libs/auth/src/angular/input-password/input-password.component.ts @@ -36,6 +36,8 @@ import { PasswordInputResult } from "./password-input-result"; /** * Determines which form input elements will be displayed in the UI. */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum InputPasswordFlow { /** * - Input: New password diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts index 945e6bbaaf5..3ea9416b7e2 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts @@ -41,6 +41,8 @@ import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper import { LoginDecryptionOptionsService } from "./login-decryption-options.service"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum State { NewUser, ExistingUserUntrustedDevice, diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts index 4c95a1eca3e..d74deb443f5 100644 --- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts +++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts @@ -40,6 +40,8 @@ import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legac import { AuthRequestApiService } from "../../common/abstractions/auth-request-api.service"; import { LoginViaAuthRequestCacheService } from "../../common/services/auth-request/default-login-via-auth-request-cache.service"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum Flow { StandardAuthRequest, // when user clicks "Login with device" from /login or "Approve from your other device" from /login-initiated AdminAuthRequest, // when user clicks "Request admin approval" from /login-initiated diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index cd226cddcec..d5180f56785 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -46,6 +46,8 @@ import { LoginComponentService, PasswordPolicies } from "./login-component.servi const BroadcasterSubscriptionId = "LoginComponent"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum LoginUiState { EMAIL_ENTRY = "EmailEntry", MASTER_PASSWORD_ENTRY = "MasterPasswordEntry", diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.component.ts b/libs/auth/src/angular/registration/registration-start/registration-start.component.ts index e365ff09aa2..44d1d720a8d 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.component.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start.component.ts @@ -26,6 +26,8 @@ import { RegistrationUserAddIcon } from "../../icons"; import { RegistrationCheckEmailIcon } from "../../icons/registration-check-email.icon"; import { RegistrationEnvSelectorComponent } from "../registration-env-selector/registration-env-selector.component"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum RegistrationStartState { USER_DATA_ENTRY = "UserDataEntry", CHECK_EMAIL = "CheckEmail", diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts index 2bb354a8cc3..c99722fb8e4 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts @@ -1,10 +1,14 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum LegacyKeyMigrationAction { PREVENT_LOGIN_AND_SHOW_REQUIRE_MIGRATION_WARNING, NAVIGATE_TO_MIGRATION_COMPONENT, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum DuoLaunchAction { DIRECT_LAUNCH, SINGLE_ACTION_POPOUT, diff --git a/libs/auth/src/angular/user-verification/active-client-verification-option.enum.ts b/libs/auth/src/angular/user-verification/active-client-verification-option.enum.ts index bceccc7f965..ef2bd1855c6 100644 --- a/libs/auth/src/angular/user-verification/active-client-verification-option.enum.ts +++ b/libs/auth/src/angular/user-verification/active-client-verification-option.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ActiveClientVerificationOption { MasterPassword = "masterPassword", Pin = "pin", diff --git a/libs/auth/src/angular/validators/compare-inputs.validator.ts b/libs/auth/src/angular/validators/compare-inputs.validator.ts index 24568ade0e3..79d547859a4 100644 --- a/libs/auth/src/angular/validators/compare-inputs.validator.ts +++ b/libs/auth/src/angular/validators/compare-inputs.validator.ts @@ -1,5 +1,7 @@ import { AbstractControl, FormGroup, ValidationErrors, ValidatorFn } from "@angular/forms"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ValidationGoal { InputsShouldMatch, InputsShouldNotMatch, diff --git a/libs/common/src/admin-console/enums/organization-api-key-type.enum.ts b/libs/common/src/admin-console/enums/organization-api-key-type.enum.ts index 44ba7f8391d..bb98ea8718b 100644 --- a/libs/common/src/admin-console/enums/organization-api-key-type.enum.ts +++ b/libs/common/src/admin-console/enums/organization-api-key-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum OrganizationApiKeyType { Default = 0, BillingSync = 1, diff --git a/libs/common/src/admin-console/enums/organization-connection-type.enum.ts b/libs/common/src/admin-console/enums/organization-connection-type.enum.ts index d2f9700a6a0..3cd11a29496 100644 --- a/libs/common/src/admin-console/enums/organization-connection-type.enum.ts +++ b/libs/common/src/admin-console/enums/organization-connection-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum OrganizationConnectionType { CloudBillingSync = 1, Scim = 2, diff --git a/libs/common/src/admin-console/enums/organization-user-status-type.enum.ts b/libs/common/src/admin-console/enums/organization-user-status-type.enum.ts index f5fa0e25c91..df9ccc8b430 100644 --- a/libs/common/src/admin-console/enums/organization-user-status-type.enum.ts +++ b/libs/common/src/admin-console/enums/organization-user-status-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum OrganizationUserStatusType { Invited = 0, Accepted = 1, diff --git a/libs/common/src/admin-console/enums/organization-user-type.enum.ts b/libs/common/src/admin-console/enums/organization-user-type.enum.ts index da50bfbdc20..098374becb4 100644 --- a/libs/common/src/admin-console/enums/organization-user-type.enum.ts +++ b/libs/common/src/admin-console/enums/organization-user-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum OrganizationUserType { Owner = 0, Admin = 1, diff --git a/libs/common/src/admin-console/enums/policy-type.enum.ts b/libs/common/src/admin-console/enums/policy-type.enum.ts index 336b834ca56..42ab798eabf 100644 --- a/libs/common/src/admin-console/enums/policy-type.enum.ts +++ b/libs/common/src/admin-console/enums/policy-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum PolicyType { TwoFactorAuthentication = 0, // Requires users to have 2fa enabled MasterPassword = 1, // Sets minimum requirements for master password complexity diff --git a/libs/common/src/admin-console/enums/provider-status-type.enum.ts b/libs/common/src/admin-console/enums/provider-status-type.enum.ts index 8da60af0eb3..32b730510e0 100644 --- a/libs/common/src/admin-console/enums/provider-status-type.enum.ts +++ b/libs/common/src/admin-console/enums/provider-status-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ProviderStatusType { Pending = 0, Created = 1, diff --git a/libs/common/src/admin-console/enums/provider-type.enum.ts b/libs/common/src/admin-console/enums/provider-type.enum.ts index eb48e362e7d..dbb60fb3638 100644 --- a/libs/common/src/admin-console/enums/provider-type.enum.ts +++ b/libs/common/src/admin-console/enums/provider-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ProviderType { Msp = 0, Reseller = 1, diff --git a/libs/common/src/admin-console/enums/provider-user-status-type.enum.ts b/libs/common/src/admin-console/enums/provider-user-status-type.enum.ts index 38d6c2e4961..253128aa25f 100644 --- a/libs/common/src/admin-console/enums/provider-user-status-type.enum.ts +++ b/libs/common/src/admin-console/enums/provider-user-status-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ProviderUserStatusType { Invited = 0, Accepted = 1, diff --git a/libs/common/src/admin-console/enums/provider-user-type.enum.ts b/libs/common/src/admin-console/enums/provider-user-type.enum.ts index 00490adcfca..d20755c3196 100644 --- a/libs/common/src/admin-console/enums/provider-user-type.enum.ts +++ b/libs/common/src/admin-console/enums/provider-user-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ProviderUserType { ProviderAdmin = 0, ServiceUser = 1, diff --git a/libs/common/src/admin-console/enums/scim-provider-type.enum.ts b/libs/common/src/admin-console/enums/scim-provider-type.enum.ts index 43c518fdfbf..cd33a1cf891 100644 --- a/libs/common/src/admin-console/enums/scim-provider-type.enum.ts +++ b/libs/common/src/admin-console/enums/scim-provider-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ScimProviderType { Default = 0, AzureAd = 1, diff --git a/libs/common/src/auth/enums/auth-request-type.ts b/libs/common/src/auth/enums/auth-request-type.ts index 31db2467861..d23fec03205 100644 --- a/libs/common/src/auth/enums/auth-request-type.ts +++ b/libs/common/src/auth/enums/auth-request-type.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AuthRequestType { AuthenticateAndUnlock = 0, Unlock = 1, diff --git a/libs/common/src/auth/enums/authentication-status.ts b/libs/common/src/auth/enums/authentication-status.ts index 17b4f1f21e8..6a6f9467ae7 100644 --- a/libs/common/src/auth/enums/authentication-status.ts +++ b/libs/common/src/auth/enums/authentication-status.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AuthenticationStatus { LoggedOut = 0, Locked = 1, diff --git a/libs/common/src/auth/enums/authentication-type.ts b/libs/common/src/auth/enums/authentication-type.ts index 35b50e6400a..13a3d70ddda 100644 --- a/libs/common/src/auth/enums/authentication-type.ts +++ b/libs/common/src/auth/enums/authentication-type.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AuthenticationType { Password = 0, Sso = 1, diff --git a/libs/common/src/auth/enums/sso.ts b/libs/common/src/auth/enums/sso.ts index 0c86a27151f..1e5766c7afd 100644 --- a/libs/common/src/auth/enums/sso.ts +++ b/libs/common/src/auth/enums/sso.ts @@ -1,25 +1,35 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum SsoType { None = 0, OpenIdConnect = 1, Saml2 = 2, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum MemberDecryptionType { MasterPassword = 0, KeyConnector = 1, TrustedDeviceEncryption = 2, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum OpenIdConnectRedirectBehavior { RedirectGet = 0, FormPost = 1, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum Saml2BindingType { HttpRedirect = 1, HttpPost = 2, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum Saml2NameIdFormat { NotConfigured = 0, Unspecified = 1, @@ -32,6 +42,8 @@ export enum Saml2NameIdFormat { Transient = 8, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum Saml2SigningBehavior { IfIdpWantAuthnRequestsSigned = 0, Always = 1, diff --git a/libs/common/src/auth/enums/two-factor-provider-type.ts b/libs/common/src/auth/enums/two-factor-provider-type.ts index b3308b6c12f..9be22bf90c8 100644 --- a/libs/common/src/auth/enums/two-factor-provider-type.ts +++ b/libs/common/src/auth/enums/two-factor-provider-type.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum TwoFactorProviderType { Authenticator = 0, Email = 1, diff --git a/libs/common/src/auth/enums/verification-type.ts b/libs/common/src/auth/enums/verification-type.ts index c1991162f91..f0ad4c0d19c 100644 --- a/libs/common/src/auth/enums/verification-type.ts +++ b/libs/common/src/auth/enums/verification-type.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum VerificationType { MasterPassword = 0, OTP = 1, diff --git a/libs/common/src/auth/models/domain/force-set-password-reason.ts b/libs/common/src/auth/models/domain/force-set-password-reason.ts index 56d52860443..68392125281 100644 --- a/libs/common/src/auth/models/domain/force-set-password-reason.ts +++ b/libs/common/src/auth/models/domain/force-set-password-reason.ts @@ -2,6 +2,8 @@ * This enum is used to determine if a user should be forced to initially set or reset their password * on login (server flag) or unlock via MP (client evaluation). */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ForceSetPasswordReason { /** * A password reset should not be forced. diff --git a/libs/common/src/auth/services/token.service.ts b/libs/common/src/auth/services/token.service.ts index c27afa6805a..61c00f69215 100644 --- a/libs/common/src/auth/services/token.service.ts +++ b/libs/common/src/auth/services/token.service.ts @@ -43,6 +43,8 @@ import { SECURITY_STAMP_MEMORY, } from "./token.state"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum TokenStorageLocation { Disk = "disk", SecureStorage = "secureStorage", diff --git a/libs/common/src/billing/enums/bitwarden-product-type.enum.ts b/libs/common/src/billing/enums/bitwarden-product-type.enum.ts index 76b0899fd9c..4389d283c00 100644 --- a/libs/common/src/billing/enums/bitwarden-product-type.enum.ts +++ b/libs/common/src/billing/enums/bitwarden-product-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum BitwardenProductType { PasswordManager = 0, SecretsManager = 1, diff --git a/libs/common/src/billing/enums/payment-method-type.enum.ts b/libs/common/src/billing/enums/payment-method-type.enum.ts index 9b29263175c..6ea98fdc5ec 100644 --- a/libs/common/src/billing/enums/payment-method-type.enum.ts +++ b/libs/common/src/billing/enums/payment-method-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum PaymentMethodType { Card = 0, BankAccount = 1, diff --git a/libs/common/src/billing/enums/plan-interval.enum.ts b/libs/common/src/billing/enums/plan-interval.enum.ts index 546336748cd..c048160310b 100644 --- a/libs/common/src/billing/enums/plan-interval.enum.ts +++ b/libs/common/src/billing/enums/plan-interval.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum PlanInterval { Monthly = 0, Annually = 1, diff --git a/libs/common/src/billing/enums/plan-sponsorship-type.enum.ts b/libs/common/src/billing/enums/plan-sponsorship-type.enum.ts index 3b4c00467c2..845b698f509 100644 --- a/libs/common/src/billing/enums/plan-sponsorship-type.enum.ts +++ b/libs/common/src/billing/enums/plan-sponsorship-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum PlanSponsorshipType { FamiliesForEnterprise = 0, } diff --git a/libs/common/src/billing/enums/plan-type.enum.ts b/libs/common/src/billing/enums/plan-type.enum.ts index c8977703454..5c356ce42fe 100644 --- a/libs/common/src/billing/enums/plan-type.enum.ts +++ b/libs/common/src/billing/enums/plan-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum PlanType { Free = 0, FamiliesAnnually2019 = 1, diff --git a/libs/common/src/billing/enums/product-tier-type.enum.ts b/libs/common/src/billing/enums/product-tier-type.enum.ts index fadf57ccdc5..e94243de588 100644 --- a/libs/common/src/billing/enums/product-tier-type.enum.ts +++ b/libs/common/src/billing/enums/product-tier-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ProductTierType { Free = 0, Families = 1, diff --git a/libs/common/src/billing/enums/product-type.enum.ts b/libs/common/src/billing/enums/product-type.enum.ts index 3072ad0f96a..0c11eb92c79 100644 --- a/libs/common/src/billing/enums/product-type.enum.ts +++ b/libs/common/src/billing/enums/product-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ProductType { PasswordManager = 0, SecretsManager = 1, diff --git a/libs/common/src/billing/enums/transaction-type.enum.ts b/libs/common/src/billing/enums/transaction-type.enum.ts index 34731a2ec33..d4c48253a39 100644 --- a/libs/common/src/billing/enums/transaction-type.enum.ts +++ b/libs/common/src/billing/enums/transaction-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum TransactionType { Charge = 0, Credit = 1, diff --git a/libs/common/src/enums/client-type.enum.ts b/libs/common/src/enums/client-type.enum.ts index 54653f74462..25e9d6f3371 100644 --- a/libs/common/src/enums/client-type.enum.ts +++ b/libs/common/src/enums/client-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ClientType { Web = "web", Browser = "browser", diff --git a/libs/common/src/enums/device-type.enum.ts b/libs/common/src/enums/device-type.enum.ts index ff6329b9ac4..d5628536ff7 100644 --- a/libs/common/src/enums/device-type.enum.ts +++ b/libs/common/src/enums/device-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum DeviceType { Android = 0, iOS = 1, diff --git a/libs/common/src/enums/event-system-user.enum.ts b/libs/common/src/enums/event-system-user.enum.ts index e2d43a4f113..f4abbb1e3e9 100644 --- a/libs/common/src/enums/event-system-user.enum.ts +++ b/libs/common/src/enums/event-system-user.enum.ts @@ -1,4 +1,6 @@ // Note: the enum key is used to describe the EventSystemUser in the UI. Be careful about changing it. +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EventSystemUser { SCIM = 1, DomainVerification = 2, diff --git a/libs/common/src/enums/event-type.enum.ts b/libs/common/src/enums/event-type.enum.ts index 51b324bb434..9efe49b1b0f 100644 --- a/libs/common/src/enums/event-type.enum.ts +++ b/libs/common/src/enums/event-type.enum.ts @@ -1,4 +1,6 @@ // Increment by 100 for each new set of events +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EventType { User_LoggedIn = 1000, User_ChangedPassword = 1001, diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index a7679a6248a..ddc75eb0d66 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -7,6 +7,8 @@ import { ServerConfig } from "../platform/abstractions/config/server-config"; * * Flags should be grouped by team to have visibility of ownership and cleanup. */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum FeatureFlag { /* Admin Console Team */ VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint", diff --git a/libs/common/src/enums/http-status-code.enum.ts b/libs/common/src/enums/http-status-code.enum.ts index 94226c82552..d2dba5d8d4c 100644 --- a/libs/common/src/enums/http-status-code.enum.ts +++ b/libs/common/src/enums/http-status-code.enum.ts @@ -4,6 +4,8 @@ * @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes} * src: https://gist.github.com/RWOverdijk/6cef816cfdf5722228e01cc05fd4b094 */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum HttpStatusCode { /** * The server has received the request headers and the client should proceed to send the request body diff --git a/libs/common/src/enums/integration-type.enum.ts b/libs/common/src/enums/integration-type.enum.ts index 42c385fe715..ac8bb9c6afa 100644 --- a/libs/common/src/enums/integration-type.enum.ts +++ b/libs/common/src/enums/integration-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum IntegrationType { Integration = "integration", SDK = "sdk", diff --git a/libs/common/src/enums/native-messaging-version.enum.ts b/libs/common/src/enums/native-messaging-version.enum.ts index f7cf411a40a..f83ec8f4b25 100644 --- a/libs/common/src/enums/native-messaging-version.enum.ts +++ b/libs/common/src/enums/native-messaging-version.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum NativeMessagingVersion { One = 1, // Original implementation Latest = One, diff --git a/libs/common/src/enums/notification-type.enum.ts b/libs/common/src/enums/notification-type.enum.ts index 0e4d0bfee3d..6d731253ce3 100644 --- a/libs/common/src/enums/notification-type.enum.ts +++ b/libs/common/src/enums/notification-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum NotificationType { SyncCipherUpdate = 0, SyncCipherCreate = 1, diff --git a/libs/common/src/enums/push-technology.enum.ts b/libs/common/src/enums/push-technology.enum.ts index 9452c144bb7..1bc4e62cc9d 100644 --- a/libs/common/src/enums/push-technology.enum.ts +++ b/libs/common/src/enums/push-technology.enum.ts @@ -1,6 +1,8 @@ /** * The preferred push technology of the server. */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum PushTechnology { /** * Indicates that we should use SignalR over web sockets to receive push notifications from the server. diff --git a/libs/common/src/key-management/vault-timeout/enums/vault-timeout-action.enum.ts b/libs/common/src/key-management/vault-timeout/enums/vault-timeout-action.enum.ts index 239a7490191..9e70b7ebb12 100644 --- a/libs/common/src/key-management/vault-timeout/enums/vault-timeout-action.enum.ts +++ b/libs/common/src/key-management/vault-timeout/enums/vault-timeout-action.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum VaultTimeoutAction { Lock = "lock", LogOut = "logOut", diff --git a/libs/common/src/platform/abstractions/environment.service.ts b/libs/common/src/platform/abstractions/environment.service.ts index 4a10f856893..b8931656848 100644 --- a/libs/common/src/platform/abstractions/environment.service.ts +++ b/libs/common/src/platform/abstractions/environment.service.ts @@ -17,6 +17,8 @@ export type Urls = { /** * A subset of available regions, additional regions can be loaded through configuration. */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum Region { US = "US", EU = "EU", diff --git a/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts index e9e68ca92c3..15655393362 100644 --- a/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts +++ b/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts @@ -46,11 +46,15 @@ export abstract class Fido2AuthenticatorService { silentCredentialDiscovery: (rpId: string) => Promise; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum Fido2AlgorithmIdentifier { ES256 = -7, RS256 = -257, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum Fido2AuthenticatorErrorCode { Unknown = "UnknownError", NotSupported = "NotSupportedError", diff --git a/libs/common/src/platform/enums/encryption-type.enum.ts b/libs/common/src/platform/enums/encryption-type.enum.ts index fd484dc2fdf..7f4b5048a45 100644 --- a/libs/common/src/platform/enums/encryption-type.enum.ts +++ b/libs/common/src/platform/enums/encryption-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EncryptionType { AesCbc256_B64 = 0, // Type 1 was the unused and removed AesCbc128_HmacSha256_B64 diff --git a/libs/common/src/platform/enums/file-upload-type.enum.ts b/libs/common/src/platform/enums/file-upload-type.enum.ts index 4cf654fd0ff..a94797b9ef3 100644 --- a/libs/common/src/platform/enums/file-upload-type.enum.ts +++ b/libs/common/src/platform/enums/file-upload-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum FileUploadType { Direct = 0, Azure = 1, diff --git a/libs/common/src/platform/enums/hash-purpose.enum.ts b/libs/common/src/platform/enums/hash-purpose.enum.ts index 887931b9edd..4b61db914a1 100644 --- a/libs/common/src/platform/enums/hash-purpose.enum.ts +++ b/libs/common/src/platform/enums/hash-purpose.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum HashPurpose { ServerAuthorization = 1, LocalAuthorization = 2, diff --git a/libs/common/src/platform/enums/html-storage-location.enum.ts b/libs/common/src/platform/enums/html-storage-location.enum.ts index 19c84fe88c7..1d018a72869 100644 --- a/libs/common/src/platform/enums/html-storage-location.enum.ts +++ b/libs/common/src/platform/enums/html-storage-location.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum HtmlStorageLocation { Local = "local", Memory = "memory", diff --git a/libs/common/src/platform/enums/key-suffix-options.enum.ts b/libs/common/src/platform/enums/key-suffix-options.enum.ts index 98fa215be6a..7cc412f563b 100644 --- a/libs/common/src/platform/enums/key-suffix-options.enum.ts +++ b/libs/common/src/platform/enums/key-suffix-options.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum KeySuffixOptions { Auto = "auto", Pin = "pin", diff --git a/libs/common/src/platform/enums/log-level-type.enum.ts b/libs/common/src/platform/enums/log-level-type.enum.ts index 709871dd5e9..b5f84467d6e 100644 --- a/libs/common/src/platform/enums/log-level-type.enum.ts +++ b/libs/common/src/platform/enums/log-level-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum LogLevelType { Debug, Info, diff --git a/libs/common/src/platform/enums/storage-location.enum.ts b/libs/common/src/platform/enums/storage-location.enum.ts index 46d50d1d38d..9f6e22babec 100644 --- a/libs/common/src/platform/enums/storage-location.enum.ts +++ b/libs/common/src/platform/enums/storage-location.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum StorageLocation { Both = "both", Disk = "disk", diff --git a/libs/common/src/platform/enums/theme-type.enum.ts b/libs/common/src/platform/enums/theme-type.enum.ts index d1767c4990a..c0f9aa2d52f 100644 --- a/libs/common/src/platform/enums/theme-type.enum.ts +++ b/libs/common/src/platform/enums/theme-type.enum.ts @@ -1,6 +1,8 @@ /** * @deprecated prefer the `ThemeTypes` constants and `Theme` type over unsafe enum types **/ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ThemeType { System = "system", Light = "light", diff --git a/libs/common/src/platform/services/cryptography/initializer-key.ts b/libs/common/src/platform/services/cryptography/initializer-key.ts index 88e36d90515..59287a62c46 100644 --- a/libs/common/src/platform/services/cryptography/initializer-key.ts +++ b/libs/common/src/platform/services/cryptography/initializer-key.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum InitializerKey { Cipher = 0, CipherView = 1, diff --git a/libs/common/src/state-migrations/migrations/28-move-provider-state-to-state-provider.ts b/libs/common/src/state-migrations/migrations/28-move-provider-state-to-state-provider.ts index 96f0a9acd5d..5ae943438d2 100644 --- a/libs/common/src/state-migrations/migrations/28-move-provider-state-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/28-move-provider-state-to-state-provider.ts @@ -5,6 +5,8 @@ import { Jsonify } from "type-fest"; import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum ProviderUserStatusType { Invited = 0, Accepted = 1, @@ -12,6 +14,8 @@ enum ProviderUserStatusType { Revoked = -1, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum ProviderUserType { ProviderAdmin = 0, ServiceUser = 1, diff --git a/libs/common/src/state-migrations/migrations/30-move-policy-state-to-state-provider.ts b/libs/common/src/state-migrations/migrations/30-move-policy-state-to-state-provider.ts index 0b4c95cb55c..f188bc0a33e 100644 --- a/libs/common/src/state-migrations/migrations/30-move-policy-state-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/30-move-policy-state-to-state-provider.ts @@ -3,6 +3,8 @@ import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum PolicyType { TwoFactorAuthentication = 0, // Requires users to have 2fa enabled MasterPassword = 1, // Sets minimum requirements for master password complexity diff --git a/libs/common/src/state-migrations/migrations/40-move-organization-state-to-state-provider.ts b/libs/common/src/state-migrations/migrations/40-move-organization-state-to-state-provider.ts index 862fe33bd4e..b23b712aefe 100644 --- a/libs/common/src/state-migrations/migrations/40-move-organization-state-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/40-move-organization-state-to-state-provider.ts @@ -7,6 +7,8 @@ import { Migrator } from "../migrator"; // Local declarations of `OrganizationData` and the types of it's properties. // Duplicated to remain frozen in time when migration occurs. +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum OrganizationUserStatusType { Invited = 0, Accepted = 1, @@ -14,6 +16,8 @@ enum OrganizationUserStatusType { Revoked = -1, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum OrganizationUserType { Owner = 0, Admin = 1, @@ -40,11 +44,15 @@ type PermissionsApi = { manageScim: boolean; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum ProviderType { Msp = 0, Reseller = 1, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum ProductType { Free = 0, Families = 1, diff --git a/libs/common/src/state-migrations/migrations/54-move-encrypted-sends.ts b/libs/common/src/state-migrations/migrations/54-move-encrypted-sends.ts index 7d3a2bebbf9..489fb2258ee 100644 --- a/libs/common/src/state-migrations/migrations/54-move-encrypted-sends.ts +++ b/libs/common/src/state-migrations/migrations/54-move-encrypted-sends.ts @@ -3,6 +3,8 @@ import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum SendType { Text = 0, File = 1, diff --git a/libs/common/src/state-migrations/migrations/59-move-kdf-config-to-state-provider.ts b/libs/common/src/state-migrations/migrations/59-move-kdf-config-to-state-provider.ts index 81ee193867b..a01ef42a8ff 100644 --- a/libs/common/src/state-migrations/migrations/59-move-kdf-config-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/59-move-kdf-config-to-state-provider.ts @@ -3,6 +3,8 @@ import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum KdfType { PBKDF2_SHA256 = 0, Argon2id = 1, diff --git a/libs/common/src/state-migrations/migrations/62-migrate-vault-timeout-settings-svc-to-state-provider.ts b/libs/common/src/state-migrations/migrations/62-migrate-vault-timeout-settings-svc-to-state-provider.ts index 5874a80dc60..3557bfae083 100644 --- a/libs/common/src/state-migrations/migrations/62-migrate-vault-timeout-settings-svc-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/62-migrate-vault-timeout-settings-svc-to-state-provider.ts @@ -57,6 +57,8 @@ const vaultTimeoutTypeRollbackRecord: Record = { onIdle: -4, }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ClientType { Web = "web", Browser = "browser", diff --git a/libs/common/src/tools/send/enums/send-type.ts b/libs/common/src/tools/send/enums/send-type.ts index 487930c90c7..5b03c71d22a 100644 --- a/libs/common/src/tools/send/enums/send-type.ts +++ b/libs/common/src/tools/send/enums/send-type.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum SendType { Text = 0, File = 1, diff --git a/libs/common/src/vault/enums/cipher-reprompt-type.ts b/libs/common/src/vault/enums/cipher-reprompt-type.ts index 1d0a523ced0..190a9bad042 100644 --- a/libs/common/src/vault/enums/cipher-reprompt-type.ts +++ b/libs/common/src/vault/enums/cipher-reprompt-type.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CipherRepromptType { None = 0, Password = 1, diff --git a/libs/common/src/vault/enums/cipher-type.ts b/libs/common/src/vault/enums/cipher-type.ts index 0b7bbf1ee17..30d80cdef7e 100644 --- a/libs/common/src/vault/enums/cipher-type.ts +++ b/libs/common/src/vault/enums/cipher-type.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CipherType { Login = 1, SecureNote = 2, diff --git a/libs/common/src/vault/enums/field-type.enum.ts b/libs/common/src/vault/enums/field-type.enum.ts index d6deb30e691..df5016890b2 100644 --- a/libs/common/src/vault/enums/field-type.enum.ts +++ b/libs/common/src/vault/enums/field-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum FieldType { Text = 0, Hidden = 1, diff --git a/libs/common/src/vault/enums/linked-id-type.enum.ts b/libs/common/src/vault/enums/linked-id-type.enum.ts index c38ebc1c6e8..b329aecb3f4 100644 --- a/libs/common/src/vault/enums/linked-id-type.enum.ts +++ b/libs/common/src/vault/enums/linked-id-type.enum.ts @@ -1,12 +1,16 @@ export type LinkedIdType = LoginLinkedId | CardLinkedId | IdentityLinkedId; // LoginView +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum LoginLinkedId { Username = 100, Password = 101, } // CardView +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CardLinkedId { CardholderName = 300, ExpMonth = 301, @@ -17,6 +21,8 @@ export enum CardLinkedId { } // IdentityView +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum IdentityLinkedId { Title = 400, MiddleName = 401, diff --git a/libs/common/src/vault/enums/secure-note-type.enum.ts b/libs/common/src/vault/enums/secure-note-type.enum.ts index 8015236d148..4fbd05e6bd4 100644 --- a/libs/common/src/vault/enums/secure-note-type.enum.ts +++ b/libs/common/src/vault/enums/secure-note-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum SecureNoteType { Generic = 0, } diff --git a/libs/common/src/vault/tasks/enums/security-task-status.enum.ts b/libs/common/src/vault/tasks/enums/security-task-status.enum.ts index 1c6e7decc20..c8c26266e66 100644 --- a/libs/common/src/vault/tasks/enums/security-task-status.enum.ts +++ b/libs/common/src/vault/tasks/enums/security-task-status.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum SecurityTaskStatus { /** * Default status for newly created tasks that have not been completed. diff --git a/libs/common/src/vault/tasks/enums/security-task-type.enum.ts b/libs/common/src/vault/tasks/enums/security-task-type.enum.ts index 264cd88696b..79a2d23c8b3 100644 --- a/libs/common/src/vault/tasks/enums/security-task-type.enum.ts +++ b/libs/common/src/vault/tasks/enums/security-task-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum SecurityTaskType { /** * Task to update a cipher's password that was found to be at-risk by an administrator diff --git a/libs/components/src/color-password/color-password.component.ts b/libs/components/src/color-password/color-password.component.ts index 86ccb41ba3c..2dd78e8525d 100644 --- a/libs/components/src/color-password/color-password.component.ts +++ b/libs/components/src/color-password/color-password.component.ts @@ -2,6 +2,8 @@ import { Component, computed, HostBinding, input } from "@angular/core"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum CharacterType { Letter, Emoji, diff --git a/libs/eslint/platform/index.mjs b/libs/eslint/platform/index.mjs index 049d95e2074..c7ea3f1dd89 100644 --- a/libs/eslint/platform/index.mjs +++ b/libs/eslint/platform/index.mjs @@ -1,3 +1,4 @@ import requiredUsing from "./required-using.mjs"; +import noEnums from "./no-enums.mjs"; -export default { rules: { "required-using": requiredUsing } }; +export default { rules: { "required-using": requiredUsing, "no-enums": noEnums } }; diff --git a/libs/eslint/platform/no-enums.mjs b/libs/eslint/platform/no-enums.mjs new file mode 100644 index 00000000000..4f3039f7e34 --- /dev/null +++ b/libs/eslint/platform/no-enums.mjs @@ -0,0 +1,23 @@ +export const errorMessage = "Enums are discouraged, please use a const object instead"; + +export default { + meta: { + type: "suggestion", + docs: { + description: "Enforce using consts instead of enums", + category: "Best Practices", + recommended: false, + }, + schema: [], + }, + create(context) { + return { + TSEnumDeclaration(node) { + context.report({ + node, + message: errorMessage, + }); + }, + }; + }, +}; diff --git a/libs/eslint/platform/no-enums.spec.mjs b/libs/eslint/platform/no-enums.spec.mjs new file mode 100644 index 00000000000..5ded12d6ed3 --- /dev/null +++ b/libs/eslint/platform/no-enums.spec.mjs @@ -0,0 +1,71 @@ +import { RuleTester } from "@typescript-eslint/rule-tester"; + +import rule, { errorMessage } from "./no-enums.mjs"; + +const ruleTester = new RuleTester({ + languageOptions: { + parserOptions: { + project: [__dirname + "/../tsconfig.spec.json"], + projectService: { + allowDefaultProject: ["*.ts*"], + }, + tsconfigRootDir: __dirname + "/..", + }, + }, +}); + +ruleTester.run("no-enums", rule.default, { + valid: [ + { + name: "Using const instead of enum", + code: ` + const Status = { + Active: "active", + Inactive: "inactive", + } as const; + `, + }, + { + name: "Using const with type", + code: ` + const Status = { + Active: "active", + Inactive: "inactive", + } as const; + type Status = typeof Status[keyof typeof Status]; + `, + }, + ], + invalid: [ + { + name: "Using enum declaration", + code: ` + enum Status { + Active = "active", + Inactive = "inactive", + } + `, + errors: [ + { + message: errorMessage, + }, + ], + }, + { + name: "Using enum with numeric values", + code: ` + enum Direction { + Up = 1, + Down = 2, + Left = 3, + Right = 4, + } + `, + errors: [ + { + message: errorMessage, + }, + ], + }, + ], +}); diff --git a/libs/importer/src/importers/fsecure/fsecure-fsk-types.ts b/libs/importer/src/importers/fsecure/fsecure-fsk-types.ts index 71797a0f8cd..1235426d683 100644 --- a/libs/importer/src/importers/fsecure/fsecure-fsk-types.ts +++ b/libs/importer/src/importers/fsecure/fsecure-fsk-types.ts @@ -6,6 +6,8 @@ export interface Data { [key: string]: FskEntry; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum FskEntryTypesEnum { Login = 1, CreditCard = 2, diff --git a/libs/importer/src/importers/lastpass/access/enums/idp-provider.ts b/libs/importer/src/importers/lastpass/access/enums/idp-provider.ts index 32e74c36ee1..01c4572fcf9 100644 --- a/libs/importer/src/importers/lastpass/access/enums/idp-provider.ts +++ b/libs/importer/src/importers/lastpass/access/enums/idp-provider.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum IdpProvider { Azure = 0, OktaAuthServer = 1, diff --git a/libs/importer/src/importers/lastpass/access/enums/lastpass-login-type.ts b/libs/importer/src/importers/lastpass/access/enums/lastpass-login-type.ts index 611dd0b6dab..a3be36c790e 100644 --- a/libs/importer/src/importers/lastpass/access/enums/lastpass-login-type.ts +++ b/libs/importer/src/importers/lastpass/access/enums/lastpass-login-type.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum LastpassLoginType { MasterPassword = 0, // Not sure what Types 1 and 2 are? diff --git a/libs/importer/src/importers/lastpass/access/enums/otp-method.ts b/libs/importer/src/importers/lastpass/access/enums/otp-method.ts index 6b940486ff0..f1237160179 100644 --- a/libs/importer/src/importers/lastpass/access/enums/otp-method.ts +++ b/libs/importer/src/importers/lastpass/access/enums/otp-method.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum OtpMethod { GoogleAuth, MicrosoftAuth, diff --git a/libs/importer/src/importers/lastpass/access/enums/platform.ts b/libs/importer/src/importers/lastpass/access/enums/platform.ts index 283e0c36ce2..6870fc28c24 100644 --- a/libs/importer/src/importers/lastpass/access/enums/platform.ts +++ b/libs/importer/src/importers/lastpass/access/enums/platform.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum Platform { Desktop, Mobile, diff --git a/libs/importer/src/importers/onepassword/types/onepassword-1pux-importer-types.ts b/libs/importer/src/importers/onepassword/types/onepassword-1pux-importer-types.ts index 63a0427b2c7..d7f4dec8f95 100644 --- a/libs/importer/src/importers/onepassword/types/onepassword-1pux-importer-types.ts +++ b/libs/importer/src/importers/onepassword/types/onepassword-1pux-importer-types.ts @@ -25,6 +25,8 @@ export interface VaultAttributes { type: string; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CategoryEnum { Login = "001", CreditCard = "002", @@ -67,6 +69,8 @@ export interface Details { password?: string | null; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum LoginFieldTypeEnum { TextOrHtml = "T", EmailAddress = "E", diff --git a/libs/importer/src/importers/protonpass/types/protonpass-json-type.ts b/libs/importer/src/importers/protonpass/types/protonpass-json-type.ts index 20fa314a314..af2eb15a740 100644 --- a/libs/importer/src/importers/protonpass/types/protonpass-json-type.ts +++ b/libs/importer/src/importers/protonpass/types/protonpass-json-type.ts @@ -27,6 +27,8 @@ export type ProtonPassItem = { pinned: boolean; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ProtonPassItemState { ACTIVE = 1, TRASHED = 2, diff --git a/libs/key-management/src/biometrics/biometrics-commands.ts b/libs/key-management/src/biometrics/biometrics-commands.ts index 81f0ea747e4..1ef31a31fb4 100644 --- a/libs/key-management/src/biometrics/biometrics-commands.ts +++ b/libs/key-management/src/biometrics/biometrics-commands.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum BiometricsCommands { /** Perform biometric authentication for the system's user. Does not require setup, and does not return cryptographic material, only yes or no. */ AuthenticateWithBiometrics = "authenticateWithBiometrics", diff --git a/libs/key-management/src/biometrics/biometrics-status.ts b/libs/key-management/src/biometrics/biometrics-status.ts index fb46ed1c4be..e436574ff7d 100644 --- a/libs/key-management/src/biometrics/biometrics-status.ts +++ b/libs/key-management/src/biometrics/biometrics-status.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum BiometricsStatus { /** For the biometrics interface, this means that biometric unlock is available and can be used. Querying for the user specifically, this means that biometric can be used for to unlock this user */ Available, diff --git a/libs/key-management/src/enums/kdf-type.enum.ts b/libs/key-management/src/enums/kdf-type.enum.ts index 29fcd9f1f8e..4f468e63a29 100644 --- a/libs/key-management/src/enums/kdf-type.enum.ts +++ b/libs/key-management/src/enums/kdf-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum KdfType { PBKDF2_SHA256 = 0, Argon2id = 1, diff --git a/libs/tools/export/vault-export/vault-export-ui/src/enums/encrypted-export-type.enum.ts b/libs/tools/export/vault-export/vault-export-ui/src/enums/encrypted-export-type.enum.ts index 4767869f7d0..2f416e4a49a 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/enums/encrypted-export-type.enum.ts +++ b/libs/tools/export/vault-export/vault-export-ui/src/enums/encrypted-export-type.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EncryptedExportType { AccountEncrypted = 0, FileEncrypted = 1, diff --git a/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts b/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts index 5da2c6f8bed..0bb753d3f37 100644 --- a/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts +++ b/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts @@ -35,6 +35,8 @@ export interface SendItemDialogParams { disableForm?: boolean; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum SendItemDialogResult { /** * A Send was saved (created or updated). diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts index c77c44467d8..9ca9aefb4ac 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts @@ -30,6 +30,8 @@ import { SendFileDetailsComponent } from "./send-file-details.component"; import { SendTextDetailsComponent } from "./send-text-details.component"; // Value = hours +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum DatePreset { OneHour = 1, OneDay = 24, diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts b/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts index 68660c4bbd9..b34d0d3a312 100644 --- a/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts +++ b/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts @@ -24,6 +24,8 @@ export interface AttachmentsDialogParams { /** * Enum representing the possible results of the attachment dialog. */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AttachmentDialogResult { Uploaded = "uploaded", Removed = "removed", diff --git a/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.ts b/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.ts index c8559799eec..dd3cbc4c5c9 100644 --- a/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.ts +++ b/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.ts @@ -34,6 +34,8 @@ import { } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AddEditFolderDialogResult { Created = "created", Deleted = "deleted", diff --git a/libs/vault/src/components/assign-collections.component.ts b/libs/vault/src/components/assign-collections.component.ts index 45d25a037e9..6a0c45cfbe3 100644 --- a/libs/vault/src/components/assign-collections.component.ts +++ b/libs/vault/src/components/assign-collections.component.ts @@ -80,6 +80,8 @@ export interface CollectionAssignmentParams { isSingleCipherAdmin?: boolean; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CollectionAssignmentResult { Saved = "saved", Canceled = "canceled", diff --git a/libs/vault/src/services/vault-nudges.service.ts b/libs/vault/src/services/vault-nudges.service.ts index be6e978eaa0..d27cd09e954 100644 --- a/libs/vault/src/services/vault-nudges.service.ts +++ b/libs/vault/src/services/vault-nudges.service.ts @@ -23,6 +23,8 @@ export type NudgeStatus = { /** * Enum to list the various nudge types, to be used by components/badges to show/hide the nudge */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum VaultNudgeType { /** Nudge to show when user has no items in their vault * Add future nudges here From 4c68f61d47a2fc8deb4300d9beec568c4a11d555 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Tue, 13 May 2025 10:58:48 -0400 Subject: [PATCH 038/245] feat(CLI-SSO-Login): [Auth/PM-21116] CLI - SSO Login - Add SSO Org Identifier option (#14605) * Add --identifier option for SSO on CLI * Add option for identifier * Moved auto-submit after the setting of client arguments * Adjusted comment * Changed to pass in as SSO option * Renamed to orgSsoIdentifier for clarity * Added more changes to orgSsoIdentifier. --- apps/cli/src/auth/commands/login.command.ts | 7 +++++- apps/cli/src/program.ts | 5 +++- libs/auth/src/angular/sso/sso.component.ts | 16 ++++++------- .../sso-redirect/sso-url.service.spec.ts | 23 +++++++++++++++++++ .../services/sso-redirect/sso-url.service.ts | 6 +++++ 5 files changed, 47 insertions(+), 10 deletions(-) diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 3ad71c62e66..cd5c8ef9bcd 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -106,6 +106,8 @@ export class LoginCommand { return Response.badRequest("client_secret is required."); } } else if (options.sso != null && this.canInteract) { + // If the optional Org SSO Identifier isn't provided, the option value is `true`. + const orgSsoIdentifier = options.sso === true ? null : options.sso; const passwordOptions: any = { type: "password", length: 64, @@ -119,7 +121,7 @@ export class LoginCommand { const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); try { - const ssoParams = await this.openSsoPrompt(codeChallenge, state); + const ssoParams = await this.openSsoPrompt(codeChallenge, state, orgSsoIdentifier); ssoCode = ssoParams.ssoCode; orgIdentifier = ssoParams.orgIdentifier; } catch { @@ -664,6 +666,7 @@ export class LoginCommand { private async openSsoPrompt( codeChallenge: string, state: string, + orgSsoIdentifier: string, ): Promise<{ ssoCode: string; orgIdentifier: string }> { const env = await firstValueFrom(this.environmentService.environment$); @@ -712,6 +715,8 @@ export class LoginCommand { this.ssoRedirectUri, state, codeChallenge, + null, + orgSsoIdentifier, ); this.platformUtilsService.launchUri(webAppSsoUrl); }); diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index dca4effcdc9..d85f1b366e6 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -118,7 +118,10 @@ export class Program extends BaseProgram { .description("Log into a user account.") .option("--method ", "Two-step login method.") .option("--code ", "Two-step login code.") - .option("--sso", "Log in with Single-Sign On.") + .option( + "--sso [identifier]", + "Log in with Single-Sign On with optional organization identifier.", + ) .option("--apikey", "Log in with an Api Key.") .option("--passwordenv ", "Environment variable storing your password") .option( diff --git a/libs/auth/src/angular/sso/sso.component.ts b/libs/auth/src/angular/sso/sso.component.ts index a91a8ed20e9..968a05bf850 100644 --- a/libs/auth/src/angular/sso/sso.component.ts +++ b/libs/auth/src/angular/sso/sso.component.ts @@ -155,7 +155,14 @@ export class SsoComponent implements OnInit { return; } - // Detect if we have landed here but only have an SSO identifier in the URL. + // Detect if we are on the first portion of the SSO flow + // and have been sent here from another client with the info in query params. + // If so, we want to initialize the SSO flow with those values. + if (this.hasParametersFromOtherClientRedirect(qParams)) { + this.initializeFromRedirectFromOtherClient(qParams); + } + + // Detect if we have landed here with an SSO identifier in the URL. // This is used by integrations that want to "short-circuit" the login to send users // directly to their IdP to simulate IdP-initiated SSO, so we submit automatically. if (qParams.identifier != null) { @@ -165,13 +172,6 @@ export class SsoComponent implements OnInit { return; } - // Detect if we are on the first portion of the SSO flow - // and have been sent here from another client with the info in query params. - // If so, we want to initialize the SSO flow with those values. - if (this.hasParametersFromOtherClientRedirect(qParams)) { - this.initializeFromRedirectFromOtherClient(qParams); - } - // Try to determine the identifier using claimed domain or local state // persisted from the user's last login attempt. await this.initializeIdentifierFromEmailOrStorage(); diff --git a/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts b/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts index 074c3a1e0b1..632a2812cfe 100644 --- a/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts +++ b/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts @@ -92,4 +92,27 @@ describe("SsoUrlService", () => { ); expect(result).toBe(expectedUrl); }); + + it("should build CLI SSO URL with Org SSO Identifier correctly", () => { + const baseUrl = "https://web-vault.bitwarden.com"; + const clientType = ClientType.Cli; + const redirectUri = "https://localhost:1000"; + const state = "abc123"; + const codeChallenge = "xyz789"; + const email = "test@bitwarden.com"; + const orgSsoIdentifier = "test-org"; + + const expectedUrl = `${baseUrl}/#/sso?clientId=cli&redirectUri=${encodeURIComponent(redirectUri)}&state=${state}&codeChallenge=${codeChallenge}&email=${encodeURIComponent(email)}&identifier=${encodeURIComponent(orgSsoIdentifier)}`; + + const result = service.buildSsoUrl( + baseUrl, + clientType, + redirectUri, + state, + codeChallenge, + email, + orgSsoIdentifier, + ); + expect(result).toBe(expectedUrl); + }); }); diff --git a/libs/auth/src/common/services/sso-redirect/sso-url.service.ts b/libs/auth/src/common/services/sso-redirect/sso-url.service.ts index 667a27ad598..b2d6231db7c 100644 --- a/libs/auth/src/common/services/sso-redirect/sso-url.service.ts +++ b/libs/auth/src/common/services/sso-redirect/sso-url.service.ts @@ -11,6 +11,7 @@ export class SsoUrlService { * @param state A state value that will be peristed through the SSO flow * @param codeChallenge A challenge value that will be used to verify the SSO code after authentication * @param email The optional email adddress of the user initiating SSO, which will be used to look up the org SSO identifier + * @param orgSsoIdentifier The optional SSO identifier of the org that is initiating SSO * @returns The URL for redirecting users to the web app SSO component */ buildSsoUrl( @@ -20,6 +21,7 @@ export class SsoUrlService { state: string, codeChallenge: string, email?: string, + orgSsoIdentifier?: string, ): string { let url = webAppUrl + @@ -36,6 +38,10 @@ export class SsoUrlService { url += "&email=" + encodeURIComponent(email); } + if (orgSsoIdentifier) { + url += "&identifier=" + encodeURIComponent(orgSsoIdentifier); + } + return url; } } From 9a7089594efac32006eacd1e013488447f1f4214 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 13 May 2025 16:59:56 +0200 Subject: [PATCH 039/245] [PM-19878] Add core-js to cli (#14696) * Add core-js to cli * Add core-js to bit-cli --- apps/cli/package.json | 1 + bitwarden_license/bit-cli/src/bw.ts | 2 ++ package-lock.json | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 82faa7d40e6..b01c96b23d1 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -71,6 +71,7 @@ "browser-hrtime": "1.1.8", "chalk": "4.1.2", "commander": "11.1.0", + "core-js": "3.40.0", "form-data": "4.0.1", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", diff --git a/bitwarden_license/bit-cli/src/bw.ts b/bitwarden_license/bit-cli/src/bw.ts index ffbc186d9e0..2e4e945b9c8 100644 --- a/bitwarden_license/bit-cli/src/bw.ts +++ b/bitwarden_license/bit-cli/src/bw.ts @@ -1,3 +1,5 @@ +import "core-js/proposals/explicit-resource-management"; + import { program } from "commander"; import { registerOssPrograms } from "@bitwarden/cli/register-oss-programs"; diff --git a/package-lock.json b/package-lock.json index 8466a4ba4c6..d1378d63ec3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -207,6 +207,7 @@ "browser-hrtime": "1.1.8", "chalk": "4.1.2", "commander": "11.1.0", + "core-js": "3.40.0", "form-data": "4.0.1", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", @@ -16960,7 +16961,6 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz", "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==", "hasInstallScript": true, - "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" From 0750ff38f580e85e7ec6296e3883ac7c38676349 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Tue, 13 May 2025 11:21:07 -0400 Subject: [PATCH 040/245] Route to vault page when user doesn't have access (#14633) --- apps/browser/src/_locales/en/messages.json | 3 +++ .../src/vault/guards/at-risk-passwords.guard.ts | 12 ++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 361093b12ef..e1a3234a61f 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5321,5 +5321,8 @@ "message": "Learn more about SSH agent", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/vault/guards/at-risk-passwords.guard.ts b/apps/browser/src/vault/guards/at-risk-passwords.guard.ts index 6bcdddfde81..fc302dd6c36 100644 --- a/apps/browser/src/vault/guards/at-risk-passwords.guard.ts +++ b/apps/browser/src/vault/guards/at-risk-passwords.guard.ts @@ -1,6 +1,6 @@ import { inject } from "@angular/core"; -import { CanActivateFn } from "@angular/router"; -import { switchMap, tap } from "rxjs"; +import { CanActivateFn, Router } from "@angular/router"; +import { map, switchMap } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -13,18 +13,22 @@ export const canAccessAtRiskPasswords: CanActivateFn = () => { const taskService = inject(TaskService); const toastService = inject(ToastService); const i18nService = inject(I18nService); + const router = inject(Router); return accountService.activeAccount$.pipe( filterOutNullish(), switchMap((user) => taskService.tasksEnabled$(user.id)), - tap((tasksEnabled) => { + map((tasksEnabled) => { if (!tasksEnabled) { toastService.showToast({ variant: "error", title: "", - message: i18nService.t("accessDenied"), + message: i18nService.t("noPermissionsViewPage"), }); + + return router.createUrlTree(["/tabs/vault"]); } + return true; }), ); }; From 1f72dd7710a160058cd39316ac00d97cdbfe5aad Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Tue, 13 May 2025 11:30:36 -0400 Subject: [PATCH 041/245] PM-21286 add aria label to nudge settings badge (#14750) --- apps/browser/src/_locales/en/messages.json | 3 +++ .../src/tools/popup/settings/settings-v2.component.html | 3 +++ 2 files changed, 6 insertions(+) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index e1a3234a61f..dcdfe7df4d6 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5258,6 +5258,9 @@ "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." }, + "nudgeBadgeAria": { + "message": "1 notification" + }, "emptyVaultNudgeTitle": { "message": "Import existing passwords" }, diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.html b/apps/browser/src/tools/popup/settings/settings-v2.component.html index 22e2d9a28d0..8d31ccf8371 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.html +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.html @@ -23,6 +23,7 @@ *ngIf="!isBrowserAutofillSettingOverridden && (showAutofillBadge$ | async)" bitBadge variant="notification" + [attr.aria-label]="'nudgeBadgeAria' | i18n" >1
@@ -53,6 +54,7 @@ *ngIf="!(showVaultBadge$ | async)?.hasBadgeDismissed" bitBadge variant="notification" + [attr.aria-label]="'nudgeBadgeAria' | i18n" >1 @@ -83,6 +85,7 @@ *ngIf="(downloadBitwardenNudgeStatus$ | async)?.hasBadgeDismissed === false" bitBadge variant="notification" + [attr.aria-label]="'nudgeBadgeAria' | i18n" >1 From 5fb46df3415aefced0b52f2db86c873962255448 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Tue, 13 May 2025 16:49:06 +0100 Subject: [PATCH 042/245] [PM 21106]Remove button not responsive for admin Console Remove Sponorship (#14743) * Resolve the remove button inactive * Resolve the lint error --- .../free-bitwarden-families.component.ts | 2 +- .../settings/sponsoring-org-row.component.ts | 2 +- .../src/services/jslib-services.module.ts | 2 +- libs/common/src/abstractions/api.service.ts | 1 - ...ion-sponsorship-api.service.abstraction.ts | 5 ++++ .../organization-sponsorship-api.service.ts | 23 ++++++++++++++++++- libs/common/src/services/api.service.ts | 12 ---------- 7 files changed, 30 insertions(+), 17 deletions(-) diff --git a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts index eb6bfdf368b..b482007e30b 100644 --- a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts +++ b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts @@ -179,7 +179,7 @@ export class FreeBitwardenFamiliesComponent implements OnInit { return; } - await this.apiService.deleteRevokeSponsorship(this.organizationId); + await this.organizationSponsorshipApiService.deleteRevokeSponsorship(this.organizationId, true); this.toastService.showToast({ variant: "success", diff --git a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts index 33e6334c577..39a7531082a 100644 --- a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts +++ b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts @@ -109,7 +109,7 @@ export class SponsoringOrgRowComponent implements OnInit { return; } - await this.apiService.deleteRevokeSponsorship(this.sponsoringOrg.id); + await this.organizationSponsorshipApiService.deleteRevokeSponsorship(this.sponsoringOrg.id); this.toastService.showToast({ variant: "success", title: null, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 470115ae3f0..3ffca776034 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1079,7 +1079,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: OrganizationSponsorshipApiServiceAbstraction, useClass: OrganizationSponsorshipApiService, - deps: [ApiServiceAbstraction], + deps: [ApiServiceAbstraction, PlatformUtilsServiceAbstraction], }), safeProvider({ provide: OrganizationBillingApiServiceAbstraction, diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 1e13a3064f4..e4453359015 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -475,7 +475,6 @@ export abstract class ApiService { getSponsorshipSyncStatus: ( sponsoredOrgId: string, ) => Promise; - deleteRevokeSponsorship: (sponsoringOrganizationId: string) => Promise; deleteRemoveSponsorship: (sponsoringOrgId: string) => Promise; postPreValidateSponsorshipToken: ( sponsorshipToken: string, diff --git a/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts b/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts index 7bd6aac17bd..5e69f57ca19 100644 --- a/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts @@ -10,4 +10,9 @@ export abstract class OrganizationSponsorshipApiServiceAbstraction { sponsoringOrgId: string, friendlyName?: string, ): Promise; + + abstract deleteRevokeSponsorship: ( + sponsoringOrganizationId: string, + isAdminInitiated?: boolean, + ) => Promise; } diff --git a/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts b/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts index 22ab3ec86ee..99440b10dec 100644 --- a/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts +++ b/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts @@ -1,12 +1,16 @@ import { ApiService } from "../../../abstractions/api.service"; import { ListResponse } from "../../../models/response/list.response"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { OrganizationSponsorshipApiServiceAbstraction } from "../../abstractions/organizations/organization-sponsorship-api.service.abstraction"; import { OrganizationSponsorshipInvitesResponse } from "../../models/response/organization-sponsorship-invites.response"; export class OrganizationSponsorshipApiService implements OrganizationSponsorshipApiServiceAbstraction { - constructor(private apiService: ApiService) {} + constructor( + private apiService: ApiService, + private platformUtilsService: PlatformUtilsService, + ) {} async getOrganizationSponsorship( sponsoredOrgId: string, ): Promise> { @@ -33,4 +37,21 @@ export class OrganizationSponsorshipApiService return await this.apiService.send("POST", url, null, true, false); } + + async deleteRevokeSponsorship( + sponsoringOrganizationId: string, + isAdminInitiated: boolean = false, + ): Promise { + const basePath = "/organization/sponsorship/"; + const hostPath = this.platformUtilsService.isSelfHost() ? "self-hosted/" : ""; + const queryParam = `?isAdminInitiated=${isAdminInitiated}`; + + return await this.apiService.send( + "DELETE", + basePath + hostPath + sponsoringOrganizationId + queryParam, + null, + true, + false, + ); + } } diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 5c4bcdedb26..639daa7c658 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -1621,18 +1621,6 @@ export class ApiService implements ApiServiceAbstraction { return new OrganizationSponsorshipSyncStatusResponse(response); } - async deleteRevokeSponsorship(sponsoringOrganizationId: string): Promise { - return await this.send( - "DELETE", - "/organization/sponsorship/" + - (this.platformUtilsService.isSelfHost() ? "self-hosted/" : "") + - sponsoringOrganizationId, - null, - true, - false, - ); - } - async deleteRemoveSponsorship(sponsoringOrgId: string): Promise { return await this.send( "DELETE", From b3df8a6c13781a41ce5b82b83b35c90c5e32e0f2 Mon Sep 17 00:00:00 2001 From: Vicki League Date: Tue, 13 May 2025 14:16:18 -0400 Subject: [PATCH 043/245] [PM-17091][PM-17043] Support system zoom in browser extension (#14435) --- .../popup/layout/popup-size.service.ts | 37 +++++++++++--- apps/browser/src/popup/app.component.ts | 3 ++ apps/browser/src/popup/scss/base.scss | 49 ++++++++++++------- 3 files changed, 63 insertions(+), 26 deletions(-) diff --git a/apps/browser/src/platform/popup/layout/popup-size.service.ts b/apps/browser/src/platform/popup/layout/popup-size.service.ts index 3ae9a633cab..69d3102d24e 100644 --- a/apps/browser/src/platform/popup/layout/popup-size.service.ts +++ b/apps/browser/src/platform/popup/layout/popup-size.service.ts @@ -50,17 +50,40 @@ export class PopupSizeService { PopupSizeService.setStyle(width); localStorage.setItem(PopupSizeService.LocalStorageKey, width); }); + } + async setHeight() { const isInChromeTab = await BrowserPopupUtils.isInTab(); + /** + * To support both browser default zoom and system default zoom, we need to take into account + * the full screen height. When system default zoom is >100%, window.innerHeight still outputs + * a height equivalent to what it would be at 100%, which can cause the extension window to + * render as too tall. So if the screen height is smaller than the max possible extension height, + * we should use that to set our extension height. Otherwise, we want to use the window.innerHeight + * to support browser zoom. + * + * This is basically a workaround for what we consider a bug with browsers reporting the wrong + * available innerHeight when system zoom is turned on. If that gets fixed, we can remove the code + * checking the screen height. + */ + const MAX_EXT_HEIGHT = 600; + const extensionInnerHeight = window.innerHeight; + // Use a 100px offset when calculating screen height to account for browser container elements + const screenAvailHeight = window.screen.availHeight - 100; + const availHeight = + screenAvailHeight < MAX_EXT_HEIGHT ? screenAvailHeight : extensionInnerHeight; + if (!BrowserPopupUtils.inPopup(window) || isInChromeTab) { - window.document.body.classList.add("body-full"); - } else if (window.innerHeight < 400) { - window.document.body.classList.add("body-xxs"); - } else if (window.innerHeight < 500) { - window.document.body.classList.add("body-xs"); - } else if (window.innerHeight < 600) { - window.document.body.classList.add("body-sm"); + window.document.documentElement.classList.add("body-full"); + } else if (availHeight < 300) { + window.document.documentElement.classList.add("body-3xs"); + } else if (availHeight < 400) { + window.document.documentElement.classList.add("body-xxs"); + } else if (availHeight < 500) { + window.document.documentElement.classList.add("body-xs"); + } else if (availHeight < 600) { + window.document.documentElement.classList.add("body-sm"); } } diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 49579f889b3..a480e1d6ba3 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -26,6 +26,7 @@ import { import { BiometricsService, BiometricStateService } from "@bitwarden/key-management"; import { PopupCompactModeService } from "../platform/popup/layout/popup-compact-mode.service"; +import { PopupSizeService } from "../platform/popup/layout/popup-size.service"; import { initPopupClosedListener } from "../platform/services/popup-view-cache-background.service"; import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service"; @@ -71,6 +72,7 @@ export class AppComponent implements OnInit, OnDestroy { private biometricStateService: BiometricStateService, private biometricsService: BiometricsService, private deviceTrustToastService: DeviceTrustToastService, + private popupSizeService: PopupSizeService, ) { this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe(); } @@ -79,6 +81,7 @@ export class AppComponent implements OnInit, OnDestroy { initPopupClosedListener(); this.compactModeService.init(); + await this.popupSizeService.setHeight(); // Component states must not persist between closing and reopening the popup, otherwise they become dead objects // Clear them aggressively to make sure this doesn't occur diff --git a/apps/browser/src/popup/scss/base.scss b/apps/browser/src/popup/scss/base.scss index 59893b5050d..80ada61f868 100644 --- a/apps/browser/src/popup/scss/base.scss +++ b/apps/browser/src/popup/scss/base.scss @@ -8,6 +8,34 @@ html { overflow: hidden; + min-height: 600px; + height: 100%; + + &.body-sm { + min-height: 500px; + } + + &.body-xs { + min-height: 400px; + } + + &.body-xxs { + min-height: 300px; + } + + &.body-3xs { + min-height: 240px; + } + + &.body-full { + min-height: unset; + width: 100%; + height: 100%; + + & body { + width: 100%; + } + } } html, @@ -20,9 +48,9 @@ body { body { width: 380px; - height: 600px; + height: 100%; position: relative; - min-height: 100vh; + min-height: inherit; overflow: hidden; color: $text-color; background-color: $background-color; @@ -31,23 +59,6 @@ body { color: themed("textColor"); background-color: themed("backgroundColor"); } - - &.body-sm { - height: 500px; - } - - &.body-xs { - height: 400px; - } - - &.body-xxs { - height: 300px; - } - - &.body-full { - width: 100%; - height: 100%; - } } h1, From 896c9bd583f772242accdd9a57dc12f1520b8d15 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Tue, 13 May 2025 13:59:03 -0500 Subject: [PATCH 044/245] [PM-20997] Part 1 of PM-20117 - Styling Changes (#14527) --- .../organization-layout.component.html | 1 + apps/web/src/locales/en/messages.json | 4 ++-- .../risk-insights/models/password-health.ts | 2 ++ .../services/risk-insights-report.service.ts | 2 ++ .../access-intelligence-routing.module.ts | 1 + .../all-applications.component.html | 5 ++--- .../all-applications.component.ts | 4 +++- .../app-table-row-scrollable.component.html | 4 ++++ .../risk-insights.component.html | 19 +++++++++++-------- 9 files changed, 28 insertions(+), 14 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html index 4e7c2403893..24be9a16090 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html @@ -6,6 +6,7 @@ icon="bwi-filter" *ngIf="organization.useRiskInsights" [text]="'accessIntelligence' | i18n" + route="access-intelligence" > org.useRiskInsights)], diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html index c9d0ba11eee..6f8e738fdc3 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html @@ -56,16 +56,15 @@ [formControl]="searchControl" > diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts index f53272845f2..c586882a1e0 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts @@ -26,6 +26,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { + IconButtonModule, Icons, NoItemsModule, SearchModule, @@ -53,6 +54,7 @@ import { ApplicationsLoadingComponent } from "./risk-insights-loading.component" NoItemsModule, SharedModule, AppTableRowScrollableComponent, + IconButtonModule, ], }) export class AllApplicationsComponent implements OnInit { @@ -160,7 +162,7 @@ export class AllApplicationsComponent implements OnInit { this.toastService.showToast({ variant: "success", title: "", - message: this.i18nService.t("appsMarkedAsCritical"), + message: this.i18nService.t("applicationsMarkedAsCriticalSuccess"), }); } finally { this.selectedUrls.clear(); diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html index b575d40076c..ff38ab5687e 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html @@ -2,6 +2,7 @@ + {{ "application" | i18n }} {{ "atRiskPasswords" | i18n }} {{ "totalPasswords" | i18n }} @@ -30,6 +31,9 @@ > + + + -
- {{ "accessIntelligence" | i18n }} -

{{ "riskInsights" | i18n }}

-
+
{{ "reviewAtRiskPasswords" | i18n }}
-
{{ "email" | i18n }}
-
{{ "atRiskPasswords" | i18n }}
+
{{ "email" | i18n }}
+
+ {{ "atRiskPasswords" | i18n }} +
@@ -117,8 +116,12 @@ "atRiskApplicationsDescription" | i18n }}
-
{{ "application" | i18n }}
-
{{ "atRiskPasswords" | i18n }}
+
+ {{ "application" | i18n }} +
+
+ {{ "atRiskPasswords" | i18n }} +
From 1cc06fd3b9ffbb1bfbfff74f286d849212aeccd7 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Tue, 13 May 2025 14:29:23 -0500 Subject: [PATCH 045/245] [PM-20999] Styling corrections to Access Intelligence - Part 2 (#14552) --- apps/web/src/locales/en/messages.json | 15 +++++ .../app-table-row-scrollable.component.html | 2 +- .../risk-insights.component.html | 63 +++++++++++-------- 3 files changed, 54 insertions(+), 26 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index b5725d4eddf..cf2174cc1db 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -140,9 +140,15 @@ "atRiskMembersDescription": { "message": "These members are logging into applications with weak, exposed, or reused passwords." }, + "atRiskMembersDescriptionNone": { + "message": "These are no members logging into applications with weak, exposed, or reused passwords." + }, "atRiskApplicationsDescription": { "message": "These applications have weak, exposed, or reused passwords." }, + "atRiskApplicationsDescriptionNone": { + "message": "These are no applications with weak, exposed, or reused passwords." + }, "atRiskMembersDescriptionWithApp": { "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", "placeholders": { @@ -152,6 +158,15 @@ } } }, + "atRiskMembersDescriptionWithAppNone": { + "message": "There are no at risk members for $APPNAME$.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html index ff38ab5687e..10dbb179519 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html @@ -89,7 +89,7 @@ diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html index fea0b32e959..f759e483bd0 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html @@ -68,19 +68,24 @@ {{ - "atRiskMembersDescription" | i18n + (dataService.atRiskMemberDetails.length > 0 + ? "atRiskMembersDescription" + : "atRiskMembersDescriptionNone" + ) | i18n }} -
-
{{ "email" | i18n }}
-
- {{ "atRiskPasswords" | i18n }} -
-
- -
-
{{ member.email }}
-
{{ member.atRiskPasswordCount }}
+ +
+
{{ "email" | i18n }}
+
+ {{ "atRiskPasswords" | i18n }} +
+ +
+
{{ member.email }}
+
{{ member.atRiskPasswordCount }}
+
+
@@ -94,7 +99,10 @@
{{ - "atRiskMembersDescriptionWithApp" | i18n: dataService.appAtRiskMembers.applicationName + (dataService.appAtRiskMembers.members.length > 0 + ? "atRiskMembersDescriptionWithApp" + : "atRiskMembersDescriptionWithAppNone" + ) | i18n: dataService.appAtRiskMembers.applicationName }}
@@ -113,21 +121,26 @@ {{ - "atRiskApplicationsDescription" | i18n + (dataService.atRiskAppDetails.length > 0 + ? "atRiskApplicationsDescription" + : "atRiskApplicationsDescriptionNone" + ) | i18n }} -
-
- {{ "application" | i18n }} -
-
- {{ "atRiskPasswords" | i18n }} -
-
- -
-
{{ app.applicationName }}
-
{{ app.atRiskPasswordCount }}
+ +
+
+ {{ "application" | i18n }} +
+
+ {{ "atRiskPasswords" | i18n }} +
+ +
+
{{ app.applicationName }}
+
{{ app.atRiskPasswordCount }}
+
+
From bacd1fb999744fdc92b99ae5563333cb2336daef Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 13 May 2025 21:36:26 +0200 Subject: [PATCH 046/245] [PM-17407] [CL-665] Remove jQuery and Popper.js (#14621) Now that the last usages of ModalService is removed from the web portions we can finally remove jQuery and Popper.js. This PR also removes bootstrap js imports since it would drag in jQuery as a peer dependency. Note: Both dependencies still exists in the lockfile as they are peer dependencies of boostrap. --- .github/renovate.json5 | 3 -- apps/web/src/app/app.component.ts | 24 +---------- apps/web/src/app/core/core.module.ts | 7 ---- apps/web/src/app/core/modal.service.ts | 56 -------------------------- apps/web/src/main.ts | 4 -- bitwarden_license/bit-web/src/main.ts | 4 -- package-lock.json | 24 ++--------- package.json | 3 -- 8 files changed, 4 insertions(+), 121 deletions(-) delete mode 100644 apps/web/src/app/core/modal.service.ts diff --git a/.github/renovate.json5 b/.github/renovate.json5 index e5cd47077fb..d0066ddd7ba 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -222,7 +222,6 @@ "@types/chrome", "@types/firefox-webext-browser", "@types/glob", - "@types/jquery", "@types/lowdb", "@types/node", "@types/node-forge", @@ -330,9 +329,7 @@ "autoprefixer", "bootstrap", "chromatic", - "jquery", "ngx-toastr", - "popper.js", "react", "react-dom", "remark-gfm", diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 55e2595e0f7..b94ce004313 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -3,25 +3,20 @@ import { DOCUMENT } from "@angular/common"; import { Component, Inject, NgZone, OnDestroy, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { NavigationEnd, Router } from "@angular/router"; -import * as jq from "jquery"; +import { Router } from "@angular/router"; import { Subject, filter, firstValueFrom, map, takeUntil, timeout } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; -import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -29,11 +24,9 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { NotificationsService } from "@bitwarden/common/platform/notifications"; import { StateEventRunnerService } from "@bitwarden/common/platform/state"; -import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KeyService, BiometricStateService } from "@bitwarden/key-management"; import { PolicyListService } from "./admin-console/core/policy-list.service"; @@ -69,8 +62,6 @@ export class AppComponent implements OnDestroy, OnInit { @Inject(DOCUMENT) private document: Document, private broadcasterService: BroadcasterService, private folderService: InternalFolderService, - private syncService: SyncService, - private passwordGenerationService: PasswordGenerationServiceAbstraction, private cipherService: CipherService, private authService: AuthService, private router: Router, @@ -85,17 +76,13 @@ export class AppComponent implements OnDestroy, OnInit { private notificationsService: NotificationsService, private stateService: StateService, private eventUploadService: EventUploadService, - private policyService: InternalPolicyService, protected policyListService: PolicyListService, - private keyConnectorService: KeyConnectorService, protected configService: ConfigService, private dialogService: DialogService, private biometricStateService: BiometricStateService, private stateEventRunnerService: StateEventRunnerService, private organizationService: InternalOrganizationServiceAbstraction, private accountService: AccountService, - private apiService: ApiService, - private appIdService: AppIdService, private processReloadService: ProcessReloadServiceAbstraction, private deviceTrustToastService: DeviceTrustToastService, ) { @@ -247,15 +234,6 @@ export class AppComponent implements OnDestroy, OnInit { }); }); - this.router.events.pipe(takeUntil(this.destroy$)).subscribe((event) => { - if (event instanceof NavigationEnd) { - const modals = Array.from(document.querySelectorAll(".modal")); - for (const modal of modals) { - (jq(modal) as any).modal("hide"); - } - } - }); - this.policyListService.addPolicies([ new TwoFactorAuthenticationPolicy(), new MasterPasswordPolicy(), diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 06a91895eb8..48e884f252c 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -26,7 +26,6 @@ import { WINDOW, } from "@bitwarden/angular/services/injection-tokens"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; -import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service"; import { RegistrationFinishService as RegistrationFinishServiceAbstraction, LoginComponentService, @@ -136,7 +135,6 @@ import { WebStorageServiceProvider } from "../platform/web-storage-service.provi import { EventService } from "./event.service"; import { InitService } from "./init.service"; import { ENV_URLS } from "./injection-tokens"; -import { ModalService } from "./modal.service"; import { RouterService } from "./router.service"; import { WebPlatformUtilsService } from "./web-platform-utils.service"; @@ -195,11 +193,6 @@ const safeProviders: SafeProvider[] = [ useClass: WebPlatformUtilsService, useAngularDecorators: true, }), - safeProvider({ - provide: ModalServiceAbstraction, - useClass: ModalService, - useAngularDecorators: true, - }), safeProvider({ provide: FileDownloadService, useClass: WebFileDownloadService, diff --git a/apps/web/src/app/core/modal.service.ts b/apps/web/src/app/core/modal.service.ts deleted file mode 100644 index 14ea6044f36..00000000000 --- a/apps/web/src/app/core/modal.service.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Injectable, Injector } from "@angular/core"; -import * as jq from "jquery"; -import { first } from "rxjs/operators"; - -import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; -import { ModalService as BaseModalService } from "@bitwarden/angular/services/modal.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; - -@Injectable() -export class ModalService extends BaseModalService { - el: any = null; - modalOpen = false; - - constructor( - injector: Injector, - private messagingService: MessagingService, - ) { - super(injector); - } - - protected setupHandlers(modalRef: ModalRef) { - modalRef.onCreated.pipe(first()).subscribe(() => { - const modals = Array.from(document.querySelectorAll(".modal")); - if (modals.length > 0) { - this.el = jq(modals[0]); - this.el.modal("show"); - - this.el.on("show.bs.modal", () => { - modalRef.show(); - this.messagingService.send("modalShow"); - }); - this.el.on("shown.bs.modal", () => { - modalRef.shown(); - this.messagingService.send("modalShown"); - if (!Utils.isMobileBrowser) { - this.el.find("*[appAutoFocus]").focus(); - } - }); - this.el.on("hide.bs.modal", () => { - this.messagingService.send("modalClose"); - }); - this.el.on("hidden.bs.modal", () => { - modalRef.closed(); - this.messagingService.send("modalClosed"); - }); - } - }); - - modalRef.onClose.pipe(first()).subscribe(() => { - if (this.el != null) { - this.el.modal("hide"); - } - }); - } -} diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts index b202a170d26..572d3968f3d 100644 --- a/apps/web/src/main.ts +++ b/apps/web/src/main.ts @@ -1,10 +1,6 @@ import { enableProdMode } from "@angular/core"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; -import "bootstrap"; -import "jquery"; -import "popper.js"; - import { AppModule } from "./app/app.module"; if (process.env.NODE_ENV === "production") { diff --git a/bitwarden_license/bit-web/src/main.ts b/bitwarden_license/bit-web/src/main.ts index b202a170d26..572d3968f3d 100644 --- a/bitwarden_license/bit-web/src/main.ts +++ b/bitwarden_license/bit-web/src/main.ts @@ -1,10 +1,6 @@ import { enableProdMode } from "@angular/core"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; -import "bootstrap"; -import "jquery"; -import "popper.js"; - import { AppModule } from "./app/app.module"; if (process.env.NODE_ENV === "production") { diff --git a/package-lock.json b/package-lock.json index d1378d63ec3..ddcdd1c9df9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,6 @@ "form-data": "4.0.1", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jquery": "3.7.1", "jsdom": "26.1.0", "jszip": "3.10.1", "koa": "2.16.1", @@ -62,7 +61,6 @@ "open": "8.4.2", "papaparse": "5.5.2", "patch-package": "8.0.0", - "popper.js": "1.16.1", "proper-lockfile": "4.1.2", "qrcode-parser": "2.1.3", "qrious": "4.0.2", @@ -102,7 +100,6 @@ "@types/firefox-webext-browser": "120.0.4", "@types/inquirer": "8.2.10", "@types/jest": "29.5.12", - "@types/jquery": "3.5.32", "@types/jsdom": "21.1.7", "@types/koa": "2.15.0", "@types/koa__multer": "2.0.7", @@ -11723,16 +11720,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/jquery": { - "version": "3.5.32", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.32.tgz", - "integrity": "sha512-b9Xbf4CkMqS02YH8zACqN1xzdxc3cO735Qe5AbSUFmyOiaWAbcpqh9Wna+Uk0vgACvoQHpWDg2rGdHkYPLmCiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/sizzle": "*" - } - }, "node_modules/@types/jsdom": { "version": "21.1.7", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", @@ -12099,13 +12086,6 @@ "@types/send": "*" } }, - "node_modules/@types/sizzle": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", - "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/sockjs": { "version": "0.3.36", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", @@ -25233,7 +25213,8 @@ "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/js-tokens": { "version": "4.0.0", @@ -31666,6 +31647,7 @@ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" diff --git a/package.json b/package.json index 2993707313f..93361275494 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "@types/firefox-webext-browser": "120.0.4", "@types/inquirer": "8.2.10", "@types/jest": "29.5.12", - "@types/jquery": "3.5.32", "@types/jsdom": "21.1.7", "@types/koa": "2.15.0", "@types/koa__multer": "2.0.7", @@ -181,7 +180,6 @@ "form-data": "4.0.1", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jquery": "3.7.1", "jsdom": "26.1.0", "jszip": "3.10.1", "koa": "2.16.1", @@ -198,7 +196,6 @@ "open": "8.4.2", "papaparse": "5.5.2", "patch-package": "8.0.0", - "popper.js": "1.16.1", "proper-lockfile": "4.1.2", "qrcode-parser": "2.1.3", "qrious": "4.0.2", From d50db0d0dded04deb261901a9672b193779d5198 Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Tue, 13 May 2025 16:08:43 -0400 Subject: [PATCH 047/245] [PM-21441] Defect - Notification bar sometimes gets cut off in fill dev (#14764) * PM-21441 * revert default value --- .../overlay-notifications-content.service.ts | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts index c21aaa37dd4..a2e1d6e49a0 100644 --- a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts +++ b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts @@ -17,7 +17,7 @@ export class OverlayNotificationsContentService private notificationBarIframeElement: HTMLIFrameElement | null = null; private currentNotificationBarType: string | null = null; private removeTabFromNotificationQueueTypes = new Set(["add", "change"]); - private notificationRefreshFlag: boolean; + private notificationRefreshFlag: boolean = false; private notificationBarElementStyles: Partial = { height: "82px", width: "430px", @@ -57,6 +57,7 @@ export class OverlayNotificationsContentService void sendExtensionMessage("checkNotificationQueue"); void sendExtensionMessage("notificationRefreshFlagValue").then((notificationRefreshFlag) => { this.notificationRefreshFlag = !!notificationRefreshFlag; + this.setNotificationRefreshBarHeight(); }); } @@ -223,15 +224,31 @@ export class OverlayNotificationsContentService this.notificationBarElement.id = "bit-notification-bar"; setElementStyles(this.notificationBarElement, this.notificationBarElementStyles, true); - - if (this.notificationRefreshFlag) { - setElementStyles(this.notificationBarElement, { height: "400px", right: "0" }, true); - } + this.setNotificationRefreshBarHeight(); this.notificationBarElement.appendChild(this.notificationBarIframeElement); } } + /** + * Sets the height of the notification bar based on the value of `notificationRefreshFlag`. + * If the flag is `true`, the bar is expanded to 400px and aligned right. + * If the flag is `false`, `null`, or `undefined`, it defaults to height of 82px. + * Skips if the notification bar element has not yet been created. + * + */ + private setNotificationRefreshBarHeight() { + const isNotificationV3 = !!this.notificationRefreshFlag; + + if (!this.notificationBarElement) { + return; + } + + if (isNotificationV3) { + setElementStyles(this.notificationBarElement, { height: "400px", right: "0" }, true); + } + } + /** * Sets up the message listener for the initialization of the notification bar. * This will send the initialization data to the notification bar iframe. From 393926beece21a23037f055bb75175dd6ff718df Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Tue, 13 May 2025 16:10:33 -0400 Subject: [PATCH 048/245] PM-21605 Remove Login text from error notification (#14767) --- .../content/components/notification/confirmation/body.ts | 1 + .../components/notification/confirmation/message.ts | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/body.ts b/apps/browser/src/autofill/content/components/notification/confirmation/body.ts index caea38718fd..8286202b498 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/body.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/body.ts @@ -48,6 +48,7 @@ export function NotificationConfirmationBody({ ? NotificationConfirmationMessage({ buttonAria, buttonText, + error, itemName, message: confirmationMessage, messageDetails, diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts index 2bf8caecfff..8fdda593382 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts @@ -8,6 +8,7 @@ import { spacing, themes, typography } from "../../constants/styles"; export type NotificationConfirmationMessageProps = { buttonAria?: string; buttonText?: string; + error?: string; itemName?: string; message?: string; messageDetails?: string; @@ -18,6 +19,7 @@ export type NotificationConfirmationMessageProps = { export function NotificationConfirmationMessage({ buttonAria, buttonText, + error, itemName, message, messageDetails, @@ -29,7 +31,11 @@ export function NotificationConfirmationMessage({ ${message || buttonText ? html`
- ${itemName} + ${!error && itemName + ? html` + ${itemName} + ` + : nothing} Date: Tue, 13 May 2025 16:49:41 -0400 Subject: [PATCH 049/245] [PM-21395] Vault Nudges Bugs (#14737) * updates to empty vault and has items nudges --- apps/browser/src/_locales/en/messages.json | 10 +++- .../vault-v2/vault-v2.component.html | 6 ++- .../components/vault-v2/vault-v2.component.ts | 11 +++- .../spotlight/spotlight.component.html | 8 ++- .../spotlight/spotlight.component.ts | 2 +- .../empty-vault-nudge.service.ts | 5 +- .../has-items-nudge.service.ts | 50 +++++++++++++------ 7 files changed, 69 insertions(+), 23 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index dcdfe7df4d6..fa300b4253b 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5273,8 +5273,14 @@ "hasItemsVaultNudgeTitle": { "message": "Welcome to your vault!" }, - "hasItemsVaultNudgeBody": { - "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" }, "newLoginNudgeTitle": { "message": "Save time with autofill" diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index 894f27245b2..b46002d645e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -44,9 +44,13 @@
+
    +
  • {{ "hasItemsVaultNudgeBodyOne" | i18n }}
  • +
  • {{ "hasItemsVaultNudgeBodyTwo" | i18n }}
  • +
  • {{ "hasItemsVaultNudgeBodyThree" | i18n }}
  • +

{{ title }}

-

+

+
- + +

{{ "autofillOptions" | i18n }} @@ -38,4 +38,4 @@ - +

diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.html b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.html index 485f8f79856..7fda078b066 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.html +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.html @@ -1,4 +1,4 @@ - +

{{ getSectionHeading() }} @@ -71,4 +71,4 @@ > - +

diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html index 0dc5e3f6ac0..98cc6489bbd 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html @@ -1,4 +1,8 @@ - +

{{ "customFields" | i18n }}

@@ -116,4 +120,4 @@ - +
diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html index 40a8954b05a..5fd3e08f22d 100644 --- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html +++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html @@ -1,4 +1,4 @@ - +

{{ "itemDetails" | i18n }}

diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html index e31be492f93..585f11c2ffe 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html @@ -1,4 +1,4 @@ - +

{{ "loginCredentials" | i18n }} @@ -127,6 +127,6 @@ > - +

diff --git a/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.html b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.html index 4e1c0c5cfd9..b919ed69f0d 100644 --- a/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.html +++ b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.html @@ -1,4 +1,4 @@ - +

{{ "typeSshKey" | i18n }} @@ -35,4 +35,4 @@ - +

diff --git a/libs/vault/src/cipher-view/additional-options/additional-options.component.html b/libs/vault/src/cipher-view/additional-options/additional-options.component.html index cc74d4e3a68..aa6d339dcd7 100644 --- a/libs/vault/src/cipher-view/additional-options/additional-options.component.html +++ b/libs/vault/src/cipher-view/additional-options/additional-options.component.html @@ -1,4 +1,4 @@ - +

{{ "additionalOptions" | i18n }}

@@ -18,4 +18,4 @@ > - +
diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html index a794946cb89..67ded3f8358 100644 --- a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html +++ b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html @@ -1,4 +1,4 @@ - +

{{ "attachments" | i18n }}

@@ -21,4 +21,4 @@ - +
diff --git a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html index aa3e05b9aab..22049b2a72e 100644 --- a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html +++ b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html @@ -1,4 +1,4 @@ - +

{{ "autofillOptions" | i18n }}

@@ -41,4 +41,4 @@ - +
diff --git a/libs/vault/src/cipher-view/card-details/card-details-view.component.html b/libs/vault/src/cipher-view/card-details/card-details-view.component.html index ff61addd7db..9d2fa45ba9e 100644 --- a/libs/vault/src/cipher-view/card-details/card-details-view.component.html +++ b/libs/vault/src/cipher-view/card-details/card-details-view.component.html @@ -1,4 +1,4 @@ - +

{{ setSectionTitle }}

@@ -93,4 +93,4 @@ > - +
diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html index 2492ed0cd81..7c60d35965f 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html @@ -1,4 +1,4 @@ - +

{{ "customFields" | i18n }}

@@ -115,4 +115,4 @@
- + diff --git a/libs/vault/src/cipher-view/item-details/item-details-v2.component.html b/libs/vault/src/cipher-view/item-details/item-details-v2.component.html index 5ba535d0436..32bf1befb66 100644 --- a/libs/vault/src/cipher-view/item-details/item-details-v2.component.html +++ b/libs/vault/src/cipher-view/item-details/item-details-v2.component.html @@ -1,4 +1,4 @@ - +

{{ "itemDetails" | i18n }}

@@ -80,4 +80,4 @@ - +
diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html index dc1168b7f01..256aec34b50 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html @@ -1,4 +1,4 @@ - +

{{ "loginCredentials" | i18n }}

@@ -164,4 +164,4 @@ > - +
diff --git a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html index 20390c0a285..f7c28ceb3f0 100644 --- a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html +++ b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html @@ -1,4 +1,4 @@ - +

{{ "typeSshKey" | i18n }}

@@ -66,4 +66,4 @@ > - +
diff --git a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html index d2154abd098..1b0a1f48f05 100644 --- a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html +++ b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html @@ -1,4 +1,4 @@ - +

{{ "personalDetails" | i18n }}

@@ -64,9 +64,9 @@ > - +
- +

{{ "identification" | i18n }}

@@ -153,9 +153,9 @@ > - +
- +

{{ "contactInfo" | i18n }}

@@ -212,4 +212,4 @@ > - +
From 3e0cc7ca7f9d1576c9122a66bff3e7e20de234ba Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Wed, 14 May 2025 09:26:47 -0500 Subject: [PATCH 051/245] [PM-18627] Remove customization settings popover (#14713) * chore: remove customization popover strings, refs PM-18627 * chore: delete new settings callout ts/html component, refs PM-18627 * chore: remove new customization code from vault-v2 component, refs PM-18627: :q * chore: delete vault-page service, refs PM-18627 * chore: add state migration to remove data, refs PM-18627 --- apps/browser/src/_locales/en/messages.json | 9 --- .../new-settings-callout.component.html | 29 ------- .../new-settings-callout.component.ts | 81 ------------------- .../components/vault-v2/vault-page.service.ts | 35 -------- .../vault-v2/vault-v2.component.html | 1 - .../components/vault-v2/vault-v2.component.ts | 5 -- libs/common/src/state-migrations/migrate.ts | 6 +- ...mization-options-callout-dismissed.spec.ts | 50 ++++++++++++ ...customization-options-callout-dismissed.ts | 23 ++++++ 9 files changed, 77 insertions(+), 162 deletions(-) delete mode 100644 apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html delete mode 100644 apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts create mode 100644 libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.spec.ts create mode 100644 libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index fa300b4253b..df35facff3c 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2208,15 +2208,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html b/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html deleted file mode 100644 index 6cc60eed6d5..00000000000 --- a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html +++ /dev/null @@ -1,29 +0,0 @@ - - - -
- {{ "newCustomizationOptionsCalloutContent" | i18n }} - - {{ "newCustomizationOptionsCalloutLink" | i18n }} - -
-
-
diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts b/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts deleted file mode 100644 index 713dc21c424..00000000000 --- a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { UserId } from "@bitwarden/common/types/guid"; -import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; -import { ButtonModule, PopoverModule } from "@bitwarden/components"; - -import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service"; -import { VaultPageService } from "../vault-page.service"; - -@Component({ - selector: "new-settings-callout", - templateUrl: "new-settings-callout.component.html", - standalone: true, - imports: [PopoverModule, JslibModule, CommonModule, ButtonModule], - providers: [VaultPageService], -}) -export class NewSettingsCalloutComponent implements OnInit, OnDestroy { - protected showNewCustomizationSettingsCallout = false; - protected activeUserId: UserId | null = null; - - constructor( - private accountService: AccountService, - private vaultProfileService: VaultProfileService, - private vaultPageService: VaultPageService, - private router: Router, - private logService: LogService, - private copyButtonService: VaultPopupCopyButtonsService, - private vaultSettingsService: VaultSettingsService, - ) {} - - async ngOnInit() { - this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - - const showQuickCopyActions = await firstValueFrom(this.copyButtonService.showQuickCopyActions$); - const clickItemsToAutofillVaultView = await firstValueFrom( - this.vaultSettingsService.clickItemsToAutofillVaultView$, - ); - - let profileCreatedDate: Date; - - try { - profileCreatedDate = await this.vaultProfileService.getProfileCreationDate(this.activeUserId); - } catch (e) { - this.logService.error("Error getting profile creation date", e); - // Default to before the cutoff date to ensure the callout is shown - profileCreatedDate = new Date("2024-12-24"); - } - - const hasCalloutBeenDismissed = await firstValueFrom( - this.vaultPageService.isCalloutDismissed(this.activeUserId), - ); - - this.showNewCustomizationSettingsCallout = - !showQuickCopyActions && - !clickItemsToAutofillVaultView && - !hasCalloutBeenDismissed && - profileCreatedDate < new Date("2024-12-25"); - } - - async goToAppearance() { - await this.router.navigate(["/appearance"]); - } - - async dismissCallout() { - if (this.activeUserId) { - await this.vaultPageService.dismissCallout(this.activeUserId); - } - } - - async ngOnDestroy() { - await this.dismissCallout(); - } -} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts deleted file mode 100644 index a7c52ed4c51..00000000000 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { inject, Injectable } from "@angular/core"; -import { map, Observable } from "rxjs"; - -import { - BANNERS_DISMISSED_DISK, - StateProvider, - UserKeyDefinition, -} from "@bitwarden/common/platform/state"; -import { UserId } from "@bitwarden/common/types/guid"; - -export const NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY = new UserKeyDefinition( - BANNERS_DISMISSED_DISK, - "newCustomizationOptionsCalloutDismissed", - { - deserializer: (calloutDismissed) => calloutDismissed, - clearOn: [], // Do not clear dismissed callouts - }, -); - -@Injectable() -export class VaultPageService { - private stateProvider = inject(StateProvider); - - isCalloutDismissed(userId: UserId): Observable { - return this.stateProvider - .getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY) - .state$.pipe(map((dismissed) => !!dismissed)); - } - - async dismissCallout(userId: UserId): Promise { - await this.stateProvider - .getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY) - .update(() => true); - } -} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index b46002d645e..43a96fc616e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -107,5 +107,4 @@ >
- diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 58a6ba0000b..ec4f3939204 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -56,9 +56,7 @@ import { NewItemDropdownV2Component, NewItemInitialValues, } from "./new-item-dropdown/new-item-dropdown-v2.component"; -import { NewSettingsCalloutComponent } from "./new-settings-callout/new-settings-callout.component"; import { VaultHeaderV2Component } from "./vault-header/vault-header-v2.component"; -import { VaultPageService } from "./vault-page.service"; import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from "."; @@ -90,12 +88,10 @@ enum VaultState { ScrollingModule, VaultHeaderV2Component, AtRiskPasswordCalloutComponent, - NewSettingsCalloutComponent, SpotlightComponent, RouterModule, TypographyModule, ], - providers: [VaultPageService], }) export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { @ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement; @@ -152,7 +148,6 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { protected noResultsIcon = Icons.NoResults; protected VaultStateEnum = VaultState; - protected showNewCustomizationSettingsCallout = false; constructor( private vaultPopupItemsService: VaultPopupItemsService, diff --git a/libs/common/src/state-migrations/migrate.ts b/libs/common/src/state-migrations/migrate.ts index b409f52d936..bea79963b0b 100644 --- a/libs/common/src/state-migrations/migrate.ts +++ b/libs/common/src/state-migrations/migrate.ts @@ -69,12 +69,13 @@ import { MoveLastSyncDate } from "./migrations/68-move-last-sync-date"; import { MigrateIncorrectFolderKey } from "./migrations/69-migrate-incorrect-folder-key"; import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account"; import { RemoveAcBannersDismissed } from "./migrations/70-remove-ac-banner-dismissed"; +import { RemoveNewCustomizationOptionsCalloutDismissed } from "./migrations/71-remove-new-customization-options-callout-dismissed"; import { MoveStateVersionMigrator } from "./migrations/8-move-state-version"; import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-settings-to-global"; import { MinVersionMigrator } from "./migrations/min-version"; export const MIN_VERSION = 3; -export const CURRENT_VERSION = 70; +export const CURRENT_VERSION = 71; export type MinVersion = typeof MIN_VERSION; export function createMigrationBuilder() { @@ -146,7 +147,8 @@ export function createMigrationBuilder() { .with(RemoveUnassignedItemsBannerDismissed, 66, 67) .with(MoveLastSyncDate, 67, 68) .with(MigrateIncorrectFolderKey, 68, 69) - .with(RemoveAcBannersDismissed, 69, CURRENT_VERSION); + .with(RemoveAcBannersDismissed, 69, 70) + .with(RemoveNewCustomizationOptionsCalloutDismissed, 70, CURRENT_VERSION); } export async function currentVersion( diff --git a/libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.spec.ts b/libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.spec.ts new file mode 100644 index 00000000000..f2c83346a62 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.spec.ts @@ -0,0 +1,50 @@ +import { runMigrator } from "../migration-helper.spec"; +import { IRREVERSIBLE } from "../migrator"; + +import { RemoveNewCustomizationOptionsCalloutDismissed } from "./71-remove-new-customization-options-callout-dismissed"; + +describe("RemoveNewCustomizationOptionsCalloutDismissed", () => { + const sut = new RemoveNewCustomizationOptionsCalloutDismissed(70, 71); + + describe("migrate", () => { + it("deletes new customization options callout dismissed from all users", async () => { + const output = await runMigrator(sut, { + global_account_accounts: { + user1: { + email: "user1@email.com", + name: "User 1", + emailVerified: true, + }, + user2: { + email: "user2@email.com", + name: "User 2", + emailVerified: true, + }, + }, + user_user1_bannersDismissed_newCustomizationOptionsCalloutDismissed: true, + user_user2_bannersDismissed_newCustomizationOptionsCalloutDismissed: true, + }); + + expect(output).toEqual({ + global_account_accounts: { + user1: { + email: "user1@email.com", + name: "User 1", + emailVerified: true, + }, + user2: { + email: "user2@email.com", + name: "User 2", + emailVerified: true, + }, + }, + }); + }); + }); + + describe("rollback", () => { + it("is irreversible", async () => { + await expect(runMigrator(sut, {}, "rollback")).rejects.toThrow(IRREVERSIBLE); + }); + }); +}); diff --git a/libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.ts b/libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.ts new file mode 100644 index 00000000000..7260048daf6 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.ts @@ -0,0 +1,23 @@ +import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; +import { IRREVERSIBLE, Migrator } from "../migrator"; + +export const SHOW_CALLOUT_KEY: KeyDefinitionLike = { + key: "newCustomizationOptionsCalloutDismissed", + stateDefinition: { name: "bannersDismissed" }, +}; + +export class RemoveNewCustomizationOptionsCalloutDismissed extends Migrator<70, 71> { + async migrate(helper: MigrationHelper): Promise { + await Promise.all( + (await helper.getAccounts()).map(async ({ userId }) => { + if (helper.getFromUser(userId, SHOW_CALLOUT_KEY) != null) { + await helper.removeFromUser(userId, SHOW_CALLOUT_KEY); + } + }), + ); + } + + async rollback(helper: MigrationHelper): Promise { + throw IRREVERSIBLE; + } +} From ad3121f5359900c16c172f3a33af31a7681a4647 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Wed, 14 May 2025 10:30:01 -0400 Subject: [PATCH 052/245] [PM-12423] Migrate Cipher Decryption to Use SDK (#14206) * Created mappings for client domain object to SDK * Add abstract decrypt observable * Added todo for future consideration * Added implementation to cipher service * Added adapter and unit tests * Created cipher encryption abstraction and service * Register cipher encryption service * Added tests for the cipher encryption service * changed signature * Updated feature flag name * added new function to be used for decrypting ciphers * Added new encryptedKey field * added new function to be used for decrypting ciphers * Manually set fields * Added encrypted key in attachment view * Fixed test * Updated references to use decrypt with feature flag * Added dependency * updated package.json * lint fix * fixed tests * Fixed small mapping issues * Fixed test * Added function to decrypt fido2 key value * Added function to decrypt fido2 key value and updated test * updated to use sdk function without prociding the key * updated localdata sdk type change * decrypt attachment content using sdk * Fixed dependency issues * updated package.json * Refactored service to handle getting decrypted buffer using the legacy and sdk implementations * updated services and component to use refactored version * Updated decryptCiphersWithSdk to use decryptManyLegacy for batch decryption, ensuring the SDK is only called once per batch * Fixed merge conflicts * Fixed merge conflicts * Fixed merge conflicts * Fixed lint issues * Moved getDecryptedAttachmentBuffer to cipher service * Moved getDecryptedAttachmentBuffer to cipher service * ensure CipherView properties are null instead of undefined * Fixed test * ensure AttachmentView properties are null instead of undefined * Linked ticket in comment * removed unused orgKey --- .../background/notification.background.ts | 4 +- .../autofill/popup/fido2/fido2.component.ts | 8 +- .../browser/src/background/main.background.ts | 9 + .../assign-collections.component.ts | 7 +- .../open-attachments.component.spec.ts | 1 + .../open-attachments.component.ts | 4 +- .../vault-password-history-v2.component.ts | 4 +- .../view-v2/view-v2.component.spec.ts | 1 + .../vault-v2/view-v2/view-v2.component.ts | 4 +- .../admin-console/commands/share.command.ts | 8 +- apps/cli/src/commands/edit.command.ts | 15 +- apps/cli/src/commands/get.command.ts | 4 +- .../service-container/service-container.ts | 9 + apps/cli/src/vault/create.command.ts | 8 +- .../services/desktop-autofill.service.ts | 4 +- .../encrypted-message-handler.service.ts | 4 +- .../vault/app/vault/attachments.component.ts | 3 + .../src/vault/app/vault/view.component.ts | 3 +- .../vault-item-dialog.component.ts | 8 +- .../components/collections.component.ts | 4 +- .../angular/src/components/share.component.ts | 8 +- .../src/services/jslib-services.module.ts | 10 + .../vault/components/add-edit.component.ts | 4 +- .../vault/components/attachments.component.ts | 49 ++- .../components/password-history.component.ts | 4 +- .../src/vault/components/view.component.ts | 23 +- libs/common/spec/utils.ts | 14 + libs/common/src/enums/feature-flag.enum.ts | 2 + .../fido2/fido2-authenticator.service.spec.ts | 8 + .../fido2/fido2-authenticator.service.ts | 4 +- .../abstractions/cipher-encryption.service.ts | 60 ++++ .../src/vault/abstractions/cipher.service.ts | 25 ++ .../models/api/cipher-permissions.api.ts | 17 + .../src/vault/models/data/cipher.data.ts | 2 +- .../vault/models/domain/attachment.spec.ts | 17 + .../src/vault/models/domain/attachment.ts | 18 + .../src/vault/models/domain/card.spec.ts | 17 + libs/common/src/vault/models/domain/card.ts | 18 + .../src/vault/models/domain/cipher.spec.ts | 167 ++++++++- libs/common/src/vault/models/domain/cipher.ts | 70 ++++ .../models/domain/fido2-credential.spec.ts | 39 ++ .../vault/models/domain/fido2-credential.ts | 25 ++ .../src/vault/models/domain/field.spec.ts | 24 +- libs/common/src/vault/models/domain/field.ts | 17 + .../src/vault/models/domain/identity.spec.ts | 28 ++ .../src/vault/models/domain/identity.ts | 30 ++ .../src/vault/models/domain/login-uri.spec.ts | 15 + .../src/vault/models/domain/login-uri.ts | 15 + .../src/vault/models/domain/login.spec.ts | 48 +++ libs/common/src/vault/models/domain/login.ts | 19 + .../src/vault/models/domain/password.spec.ts | 13 + .../src/vault/models/domain/password.ts | 14 + .../vault/models/domain/secure-note.spec.ts | 13 + .../src/vault/models/domain/secure-note.ts | 13 + .../src/vault/models/domain/ssh-key.spec.ts | 13 + .../common/src/vault/models/domain/ssh-key.ts | 15 + .../vault/models/view/attachment.view.spec.ts | 55 +++ .../src/vault/models/view/attachment.view.ts | 40 +++ .../common/src/vault/models/view/card.view.ts | 13 + .../src/vault/models/view/cipher.view.spec.ts | 132 ++++++- .../src/vault/models/view/cipher.view.ts | 68 +++- .../models/view/fido2-credential.view.ts | 27 ++ .../src/vault/models/view/field.view.ts | 19 + .../src/vault/models/view/identity.view.ts | 13 + .../vault/models/view/login-uri-view.spec.ts | 22 ++ .../src/vault/models/view/login-uri.view.ts | 17 + .../src/vault/models/view/login.view.spec.ts | 35 +- .../src/vault/models/view/login.view.ts | 25 ++ .../models/view/password-history.view.spec.ts | 23 ++ .../models/view/password-history.view.ts | 17 + .../src/vault/models/view/secure-note.view.ts | 13 + .../src/vault/models/view/ssh-key.view.ts | 17 + .../src/vault/services/cipher.service.spec.ts | 87 ++++- .../src/vault/services/cipher.service.ts | 89 ++++- .../default-cipher-encryption.service.spec.ts | 334 ++++++++++++++++++ .../default-cipher-encryption.service.ts | 190 ++++++++++ .../bitwarden/bitwarden-json-importer.ts | 4 +- .../individual-vault-export.service.spec.ts | 15 +- .../individual-vault-export.service.ts | 36 +- .../src/services/org-vault-export.service.ts | 9 +- .../cipher-attachments.component.spec.ts | 1 + .../cipher-attachments.component.ts | 8 +- .../services/default-cipher-form.service.ts | 14 +- .../download-attachment.component.spec.ts | 35 +- .../download-attachment.component.ts | 41 +-- 85 files changed, 2171 insertions(+), 218 deletions(-) create mode 100644 libs/common/src/vault/abstractions/cipher-encryption.service.ts create mode 100644 libs/common/src/vault/services/default-cipher-encryption.service.spec.ts create mode 100644 libs/common/src/vault/services/default-cipher-encryption.service.ts diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 52920ec67a8..a73141b7e4d 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -894,9 +894,7 @@ export default class NotificationBackground { private async getDecryptedCipherById(cipherId: string, userId: UserId) { const cipher = await this.cipherService.get(cipherId, userId); if (cipher != null && cipher.type === CipherType.Login) { - return await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId), - ); + return await this.cipherService.decrypt(cipher, userId); } return null; } diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index 0471d460fd5..6b7d9120195 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -216,9 +216,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.ciphers = await Promise.all( message.cipherIds.map(async (cipherId) => { const cipher = await this.cipherService.get(cipherId, activeUserId); - return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + return this.cipherService.decrypt(cipher, activeUserId); }), ); @@ -237,9 +235,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.ciphers = await Promise.all( message.existingCipherIds.map(async (cipherId) => { const cipher = await this.cipherService.get(cipherId, activeUserId); - return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + return this.cipherService.decrypt(cipher, activeUserId); }), ); diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 85a9cd27c57..a724f857cd1 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -183,6 +183,7 @@ import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-st import { SendService } from "@bitwarden/common/tools/send/services/send.service"; import { InternalSendService as InternalSendServiceAbstraction } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; @@ -199,6 +200,7 @@ import { DefaultCipherAuthorizationService, } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; @@ -408,6 +410,7 @@ export default class MainBackground { endUserNotificationService: EndUserNotificationService; inlineMenuFieldQualificationService: InlineMenuFieldQualificationService; taskService: TaskService; + cipherEncryptionService: CipherEncryptionService; ipcContentScriptManagerService: IpcContentScriptManagerService; ipcService: IpcService; @@ -856,6 +859,11 @@ export default class MainBackground { this.bulkEncryptService = new FallbackBulkEncryptService(this.encryptService); + this.cipherEncryptionService = new DefaultCipherEncryptionService( + this.sdkService, + this.logService, + ); + this.cipherService = new CipherService( this.keyService, this.domainSettingsService, @@ -871,6 +879,7 @@ export default class MainBackground { this.stateProvider, this.accountService, this.logService, + this.cipherEncryptionService, ); this.folderService = new FolderService( this.keyService, diff --git a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts index 27f3b7e5e18..7052be5ea62 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts @@ -11,7 +11,6 @@ import { CollectionService } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; -import { OrgKey, UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { @@ -66,11 +65,7 @@ export class AssignCollections { route.queryParams.pipe( switchMap(async ({ cipherId }) => { const cipherDomain = await this.cipherService.get(cipherId, userId); - const key: UserKey | OrgKey = await this.cipherService.getKeyForCipherKeyDecryption( - cipherDomain, - userId, - ); - return cipherDomain.decrypt(key); + return await this.cipherService.decrypt(cipherDomain, userId); }), ), ), diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts index 66d9096cd5c..ec5c93feb9e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts @@ -81,6 +81,7 @@ describe("OpenAttachmentsComponent", () => { useValue: { get: getCipher, getKeyForCipherKeyDecryption: () => Promise.resolve(null), + decrypt: jest.fn().mockResolvedValue(cipherView), }, }, { diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts index 1bc7e22e6d5..9189ea51313 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts @@ -81,9 +81,7 @@ export class OpenAttachmentsComponent implements OnInit { this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); - const cipher = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), - ); + const cipher = await this.cipherService.decrypt(cipherDomain, activeUserId); if (!cipher.organizationId) { this.cipherIsAPartOfFreeOrg = false; diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts index 5d315775b10..d0eef20f044 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts @@ -69,8 +69,6 @@ export class PasswordHistoryV2Component implements OnInit { const activeUserId = activeAccount.id as UserId; const cipher = await this.cipherService.get(cipherId, activeUserId); - this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(cipher, activeUserId); } } diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts index 44874221a59..3222f39a162 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts @@ -82,6 +82,7 @@ describe("ViewV2Component", () => { getKeyForCipherKeyDecryption: jest.fn().mockResolvedValue({}), deleteWithServer: jest.fn().mockResolvedValue(undefined), softDeleteWithServer: jest.fn().mockResolvedValue(undefined), + decrypt: jest.fn().mockResolvedValue(mockCipher), }; beforeEach(async () => { diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index a834314560b..0a71caf5aee 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -203,9 +203,7 @@ export class ViewV2Component { async getCipherData(id: string, userId: UserId) { const cipher = await this.cipherService.get(id, userId); - return await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId), - ); + return await this.cipherService.decrypt(cipher, userId); } async editCipher() { diff --git a/apps/cli/src/admin-console/commands/share.command.ts b/apps/cli/src/admin-console/commands/share.command.ts index 6d9e6c8b6c0..540bc2659c9 100644 --- a/apps/cli/src/admin-console/commands/share.command.ts +++ b/apps/cli/src/admin-console/commands/share.command.ts @@ -59,15 +59,11 @@ export class ShareCommand { return Response.badRequest("This item already belongs to an organization."); } - const cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const cipherView = await this.cipherService.decrypt(cipher, activeUserId); try { await this.cipherService.shareWithServer(cipherView, organizationId, req, activeUserId); const updatedCipher = await this.cipherService.get(cipher.id, activeUserId); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index 2d4a854135d..4dcf805661d 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -90,9 +90,7 @@ export class EditCommand { return Response.notFound(); } - let cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + let cipherView = await this.cipherService.decrypt(cipher, activeUserId); if (cipherView.isDeleted) { return Response.badRequest("You may not edit a deleted item. Use the restore command first."); } @@ -100,9 +98,7 @@ export class EditCommand { const encCipher = await this.cipherService.encrypt(cipherView, activeUserId); try { const updatedCipher = await this.cipherService.updateWithServer(encCipher); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { @@ -132,12 +128,7 @@ export class EditCommand { cipher, activeUserId, ); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption( - updatedCipher, - await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)), - ), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 1bdbd051585..c3ba6044f8a 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -116,9 +116,7 @@ export class GetCommand extends DownloadCommand { if (Utils.isGuid(id)) { const cipher = await this.cipherService.get(id, activeUserId); if (cipher != null) { - decCipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + decCipher = await this.cipherService.decrypt(cipher, activeUserId); } } else if (id.trim() !== "") { let ciphers = await this.cipherService.getAllDecrypted(activeUserId); diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index fe2f506f229..cdf6c4bbfda 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -139,12 +139,14 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-state.provider"; import { SendService } from "@bitwarden/common/tools/send/services/send.service"; import { UserId } from "@bitwarden/common/types/guid"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherAuthorizationService, DefaultCipherAuthorizationService, } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; @@ -284,6 +286,7 @@ export class ServiceContainer { ssoUrlService: SsoUrlService; masterPasswordApiService: MasterPasswordApiServiceAbstraction; bulkEncryptService: FallbackBulkEncryptService; + cipherEncryptionService: CipherEncryptionService; constructor() { let p = null; @@ -679,6 +682,11 @@ export class ServiceContainer { this.accountService, ); + this.cipherEncryptionService = new DefaultCipherEncryptionService( + this.sdkService, + this.logService, + ); + this.cipherService = new CipherService( this.keyService, this.domainSettingsService, @@ -694,6 +702,7 @@ export class ServiceContainer { this.stateProvider, this.accountService, this.logService, + this.cipherEncryptionService, ); this.folderService = new FolderService( diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 5b34d2cb507..b1536e23748 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -93,9 +93,7 @@ export class CreateCommand { const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId); try { const newCipher = await this.cipherService.createWithServer(cipher); - const decCipher = await newCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(newCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(newCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { @@ -162,9 +160,7 @@ export class CreateCommand { new Uint8Array(fileBuf).buffer, activeUserId, ); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); return Response.success(new CipherResponse(decCipher)); } catch (e) { return Response.error(e); diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts index e88e16c2ffc..d6dddf3b23f 100644 --- a/apps/desktop/src/autofill/services/desktop-autofill.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts @@ -199,9 +199,7 @@ export class DesktopAutofillService implements OnDestroy { return; } - const decrypted = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const decrypted = await this.cipherService.decrypt(cipher, activeUserId); const fido2Credential = decrypted.login.fido2Credentials?.[0]; if (!fido2Credential) { diff --git a/apps/desktop/src/services/encrypted-message-handler.service.ts b/apps/desktop/src/services/encrypted-message-handler.service.ts index 591ff6fa8cf..37a8114c1d1 100644 --- a/apps/desktop/src/services/encrypted-message-handler.service.ts +++ b/apps/desktop/src/services/encrypted-message-handler.service.ts @@ -207,9 +207,7 @@ export class EncryptedMessageHandlerService { return { status: "failure" }; } - const cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const cipherView = await this.cipherService.decrypt(cipher, activeUserId); cipherView.name = credentialUpdatePayload.name; cipherView.login.password = credentialUpdatePayload.password; cipherView.login.username = credentialUpdatePayload.userName; diff --git a/apps/desktop/src/vault/app/vault/attachments.component.ts b/apps/desktop/src/vault/app/vault/attachments.component.ts index ea4f49b8431..a2cea5f2722 100644 --- a/apps/desktop/src/vault/app/vault/attachments.component.ts +++ b/apps/desktop/src/vault/app/vault/attachments.component.ts @@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -33,6 +34,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { billingAccountProfileStateService: BillingAccountProfileStateService, accountService: AccountService, toastService: ToastService, + configService: ConfigService, ) { super( cipherService, @@ -49,6 +51,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { billingAccountProfileStateService, accountService, toastService, + configService, ); } } diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index e5f677cbca6..e74b07445da 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -72,7 +72,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro accountService: AccountService, toastService: ToastService, cipherAuthorizationService: CipherAuthorizationService, - private configService: ConfigService, + configService: ConfigService, ) { super( cipherService, @@ -100,6 +100,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro billingAccountProfileStateService, toastService, cipherAuthorizationService, + configService, ); } diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index 10c35f861b9..aa457e97093 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -481,9 +481,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { activeUserId, ); - updatedCipherView = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), - ); + updatedCipherView = await this.cipherService.decrypt(updatedCipher, activeUserId); } this.cipherFormComponent.patchCipher((currentCipher) => { @@ -520,9 +518,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { return; } const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - return await config.originalCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(config.originalCipher, activeUserId), - ); + return await this.cipherService.decrypt(config.originalCipher, activeUserId); } private updateTitle() { diff --git a/libs/angular/src/admin-console/components/collections.component.ts b/libs/angular/src/admin-console/components/collections.component.ts index 5f39966468f..8ae90705f92 100644 --- a/libs/angular/src/admin-console/components/collections.component.ts +++ b/libs/angular/src/admin-console/components/collections.component.ts @@ -50,9 +50,7 @@ export class CollectionsComponent implements OnInit { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); this.cipherDomain = await this.loadCipher(activeUserId); this.collectionIds = this.loadCipherCollections(); - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); this.collections = await this.loadCollections(); this.collections.forEach((c) => ((c as any).checked = false)); diff --git a/libs/angular/src/components/share.component.ts b/libs/angular/src/components/share.component.ts index e785441b8e4..198cc7dc3a5 100644 --- a/libs/angular/src/components/share.component.ts +++ b/libs/angular/src/components/share.component.ts @@ -76,9 +76,7 @@ export class ShareComponent implements OnInit, OnDestroy { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); - this.cipher = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(cipherDomain, activeUserId); } filterCollections() { @@ -105,9 +103,7 @@ export class ShareComponent implements OnInit, OnDestroy { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); - const cipherView = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), - ); + const cipherView = await this.cipherService.decrypt(cipherDomain, activeUserId); const orgs = await firstValueFrom(this.organizations$); const orgName = orgs.find((o) => o.id === this.organizationId)?.name ?? this.i18nService.t("organization"); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 3ffca776034..920d35a1017 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -263,6 +263,7 @@ import { InternalSendService, SendService as SendServiceAbstraction, } from "@bitwarden/common/tools/send/services/send.service.abstraction"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; @@ -281,6 +282,7 @@ import { DefaultCipherAuthorizationService, } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; @@ -509,6 +511,7 @@ const safeProviders: SafeProvider[] = [ stateProvider: StateProvider, accountService: AccountServiceAbstraction, logService: LogService, + cipherEncryptionService: CipherEncryptionService, ) => new CipherService( keyService, @@ -525,6 +528,7 @@ const safeProviders: SafeProvider[] = [ stateProvider, accountService, logService, + cipherEncryptionService, ), deps: [ KeyService, @@ -541,6 +545,7 @@ const safeProviders: SafeProvider[] = [ StateProvider, AccountServiceAbstraction, LogService, + CipherEncryptionService, ], }), safeProvider({ @@ -1528,6 +1533,11 @@ const safeProviders: SafeProvider[] = [ useClass: MasterPasswordApiService, deps: [ApiServiceAbstraction, LogService], }), + safeProvider({ + provide: CipherEncryptionService, + useClass: DefaultCipherEncryptionService, + deps: [SdkService, LogService], + }), ]; @NgModule({ diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index b9defa8383d..b04adc1fdfb 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -269,9 +269,7 @@ export class AddEditComponent implements OnInit, OnDestroy { if (this.cipher == null) { if (this.editMode) { const cipher = await this.loadCipher(activeUserId); - this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(cipher, activeUserId); // Adjust Cipher Name if Cloning if (this.cloneMode) { diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index 9e9450c587e..e4b01d3aac1 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -9,13 +9,13 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { UserId } from "@bitwarden/common/types/guid"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; @@ -56,6 +56,7 @@ export class AttachmentsComponent implements OnInit { protected billingAccountProfileStateService: BillingAccountProfileStateService, protected accountService: AccountService, protected toastService: ToastService, + protected configService: ConfigService, ) {} async ngOnInit() { @@ -88,9 +89,7 @@ export class AttachmentsComponent implements OnInit { const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.formPromise = this.saveCipherAttachment(files[0], activeUserId); this.cipherDomain = await this.formPromise; - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); this.toastService.showToast({ variant: "success", title: null, @@ -130,9 +129,7 @@ export class AttachmentsComponent implements OnInit { const updatedCipher = await this.deletePromises[attachment.id]; const cipher = new Cipher(updatedCipher); - this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(cipher, activeUserId); this.toastService.showToast({ variant: "success", @@ -197,12 +194,14 @@ export class AttachmentsComponent implements OnInit { } try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = - attachment.key != null - ? attachment.key - : await this.keyService.getOrgKey(this.cipher.organizationId); - const decBuf = await this.encryptService.decryptFileData(encBuf, key); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( + this.cipherDomain.id as CipherId, + attachment, + response, + activeUserId, + ); + this.fileDownloadService.download({ fileName: attachment.fileName, blobData: decBuf, @@ -228,9 +227,7 @@ export class AttachmentsComponent implements OnInit { protected async init() { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); this.cipherDomain = await this.loadCipher(activeUserId); - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); const canAccessPremium = await firstValueFrom( this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), @@ -276,15 +273,17 @@ export class AttachmentsComponent implements OnInit { try { // 2. Resave - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = - attachment.key != null - ? attachment.key - : await this.keyService.getOrgKey(this.cipher.organizationId); - const decBuf = await this.encryptService.decryptFileData(encBuf, key); const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(getUserId), ); + + const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( + this.cipherDomain.id as CipherId, + attachment, + response, + activeUserId, + ); + this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer( this.cipherDomain, attachment.fileName, @@ -292,9 +291,7 @@ export class AttachmentsComponent implements OnInit { activeUserId, admin, ); - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); // 3. Delete old this.deletePromises[attachment.id] = this.deleteCipherAttachment( diff --git a/libs/angular/src/vault/components/password-history.component.ts b/libs/angular/src/vault/components/password-history.component.ts index 4df9f4bd24d..acb89b82191 100644 --- a/libs/angular/src/vault/components/password-history.component.ts +++ b/libs/angular/src/vault/components/password-history.component.ts @@ -42,9 +42,7 @@ export class PasswordHistoryComponent implements OnInit { protected async init() { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const cipher = await this.cipherService.get(this.cipherId, activeUserId); - const decCipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(cipher, activeUserId); this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory; } } diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 9d5a8fe9e62..8915cb6b671 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -34,13 +34,13 @@ import { EventType } from "@bitwarden/common/enums"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { CollectionId, UserId } from "@bitwarden/common/types/guid"; +import { CipherId, CollectionId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; @@ -137,6 +137,7 @@ export class ViewComponent implements OnDestroy, OnInit { private billingAccountProfileStateService: BillingAccountProfileStateService, protected toastService: ToastService, private cipherAuthorizationService: CipherAuthorizationService, + protected configService: ConfigService, ) {} ngOnInit() { @@ -458,19 +459,19 @@ export class ViewComponent implements OnDestroy, OnInit { } try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = - attachment.key != null - ? attachment.key - : await this.keyService.getOrgKey(this.cipher.organizationId); - const decBuf = await this.encryptService.decryptFileData(encBuf, key); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( + this.cipher.id as CipherId, + attachment, + response, + activeUserId, + ); + this.fileDownloadService.download({ fileName: attachment.fileName, blobData: decBuf, }); - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e) { + } catch { this.toastService.showToast({ variant: "error", title: null, diff --git a/libs/common/spec/utils.ts b/libs/common/spec/utils.ts index 51db65d0ce0..2b9b2567895 100644 --- a/libs/common/spec/utils.ts +++ b/libs/common/spec/utils.ts @@ -64,6 +64,20 @@ export function makeSymmetricCryptoKey( */ export const mockFromJson = (stub: any) => (stub + "_fromJSON") as any; +/** + * Use to mock a return value of a static fromSdk method. + */ +export const mockFromSdk = (stub: any) => { + if (typeof stub === "object") { + return { + ...stub, + __fromSdk: true, + }; + } + + return `${stub}_fromSdk`; +}; + /** * Tracks the emissions of the given observable. * diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index ddc75eb0d66..d349703bddf 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -57,6 +57,7 @@ export enum FeatureFlag { PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge", PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form", SecurityTasks = "security-tasks", + PM19941MigrateCipherDomainToSdk = "pm-19941-migrate-cipher-domain-to-sdk", CipherKeyEncryption = "cipher-key-encryption", PM18520_UpdateDesktopCipherForm = "pm-18520-desktop-cipher-forms", EndUserNotifications = "pm-10609-end-user-notifications", @@ -111,6 +112,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.CipherKeyEncryption]: FALSE, [FeatureFlag.PM18520_UpdateDesktopCipherForm]: FALSE, [FeatureFlag.EndUserNotifications]: FALSE, + [FeatureFlag.PM19941MigrateCipherDomainToSdk]: FALSE, /* Auth */ [FeatureFlag.PM9115_TwoFactorExtensionDataPersistence]: FALSE, diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts index 3ea86a1f504..5c377e1a980 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts @@ -152,6 +152,7 @@ describe("FidoAuthenticatorService", () => { id === excludedCipher.id ? ({ decrypt: () => excludedCipher } as any) : undefined, ); cipherService.getAllDecrypted.mockResolvedValue([excludedCipher]); + cipherService.decrypt.mockResolvedValue(excludedCipher); }); /** @@ -220,6 +221,7 @@ describe("FidoAuthenticatorService", () => { id === existingCipher.id ? ({ decrypt: () => existingCipher } as any) : undefined, ); cipherService.getAllDecrypted.mockResolvedValue([existingCipher]); + cipherService.decrypt.mockResolvedValue(existingCipher); }); /** @@ -306,6 +308,11 @@ describe("FidoAuthenticatorService", () => { const encryptedCipher = { ...existingCipher, reprompt: CipherRepromptType.Password }; cipherService.get.mockResolvedValue(encryptedCipher as unknown as Cipher); + cipherService.decrypt.mockResolvedValue({ + ...existingCipher, + reprompt: CipherRepromptType.Password, + } as unknown as CipherView); + const result = async () => await authenticator.makeCredential(params, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Unknown); @@ -347,6 +354,7 @@ describe("FidoAuthenticatorService", () => { cipherId === cipher.id ? ({ decrypt: () => cipher } as any) : undefined, ); cipherService.getAllDecrypted.mockResolvedValue([await cipher]); + cipherService.decrypt.mockResolvedValue(cipher); cipherService.encrypt.mockImplementation(async (cipher) => { cipher.login.fido2Credentials[0].credentialId = credentialId; // Replace id for testability return {} as any; diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts index 76bd19b2876..a605e466338 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts @@ -151,9 +151,7 @@ export class Fido2AuthenticatorService ); const encrypted = await this.cipherService.get(cipherId, activeUserId); - cipher = await encrypted.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(encrypted, activeUserId), - ); + cipher = await this.cipherService.decrypt(encrypted, activeUserId); if ( !userVerified && diff --git a/libs/common/src/vault/abstractions/cipher-encryption.service.ts b/libs/common/src/vault/abstractions/cipher-encryption.service.ts new file mode 100644 index 00000000000..6b2a8e8943e --- /dev/null +++ b/libs/common/src/vault/abstractions/cipher-encryption.service.ts @@ -0,0 +1,60 @@ +import { CipherListView } from "@bitwarden/sdk-internal"; + +import { UserId } from "../../types/guid"; +import { Cipher } from "../models/domain/cipher"; +import { AttachmentView } from "../models/view/attachment.view"; +import { CipherView } from "../models/view/cipher.view"; + +/** + * Service responsible for encrypting and decrypting ciphers. + */ +export abstract class CipherEncryptionService { + /** + * Decrypts a cipher using the SDK for the given userId. + * + * @param cipher The encrypted cipher object + * @param userId The user ID whose key will be used for decryption + * + * @returns A promise that resolves to the decrypted cipher view + */ + abstract decrypt(cipher: Cipher, userId: UserId): Promise; + /** + * Decrypts many ciphers using the SDK for the given userId. + * + * For bulk decryption, prefer using `decryptMany`, which returns a more efficient + * `CipherListView` object. + * + * @param ciphers The encrypted cipher objects + * @param userId The user ID whose key will be used for decryption + * + * @deprecated Use `decryptMany` for bulk decryption instead. + * + * @returns A promise that resolves to an array of decrypted cipher views + */ + abstract decryptManyLegacy(ciphers: Cipher[], userId: UserId): Promise; + /** + * Decrypts many ciphers using the SDK for the given userId. + * + * @param ciphers The encrypted cipher objects + * @param userId The user ID whose key will be used for decryption + * + * @returns A promise that resolves to an array of decrypted cipher list views + */ + abstract decryptMany(ciphers: Cipher[], userId: UserId): Promise; + /** + * Decrypts an attachment's content from a response object. + * + * @param cipher The encrypted cipher object that owns the attachment + * @param attachment The attachment view object + * @param encryptedContent The encrypted content of the attachment + * @param userId The user ID whose key will be used for decryption + * + * @returns A promise that resolves to the decrypted content + */ + abstract decryptAttachmentContent( + cipher: Cipher, + attachment: AttachmentView, + encryptedContent: Uint8Array, + userId: UserId, + ): Promise; +} diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index b12488a5e03..a67dfcef8b9 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -14,6 +14,7 @@ import { LocalData } from "../models/data/local.data"; import { Cipher } from "../models/domain/cipher"; import { Field } from "../models/domain/field"; import { CipherWithIdRequest } from "../models/request/cipher-with-id.request"; +import { AttachmentView } from "../models/view/attachment.view"; import { CipherView } from "../models/view/cipher.view"; import { FieldView } from "../models/view/field.view"; import { AddEditCipherInfo } from "../types/add-edit-cipher-info"; @@ -215,4 +216,28 @@ export abstract class CipherService implements UserKeyRotationDataProvider; abstract getNextCardCipher(userId: UserId): Promise; abstract getNextIdentityCipher(userId: UserId): Promise; + + /** + * Decrypts a cipher using either the SDK or the legacy method based on the feature flag. + * @param cipher The cipher to decrypt. + * @param userId The user ID to use for decryption. + * @returns A promise that resolves to the decrypted cipher view. + */ + abstract decrypt(cipher: Cipher, userId: UserId): Promise; + /** + * Decrypts an attachment's content from a response object. + * + * @param cipherId The ID of the cipher that owns the attachment + * @param attachment The attachment view object + * @param response The response object containing the encrypted content + * @param userId The user ID whose key will be used for decryption + * + * @returns A promise that resolves to the decrypted content + */ + abstract getDecryptedAttachmentBuffer( + cipherId: CipherId, + attachment: AttachmentView, + response: Response, + userId: UserId, + ): Promise; } diff --git a/libs/common/src/vault/models/api/cipher-permissions.api.ts b/libs/common/src/vault/models/api/cipher-permissions.api.ts index 4df7f891e26..b7341d39b1d 100644 --- a/libs/common/src/vault/models/api/cipher-permissions.api.ts +++ b/libs/common/src/vault/models/api/cipher-permissions.api.ts @@ -1,5 +1,7 @@ import { Jsonify } from "type-fest"; +import { CipherPermissions as SdkCipherPermissions } from "@bitwarden/sdk-internal"; + import { BaseResponse } from "../../../models/response/base.response"; export class CipherPermissionsApi extends BaseResponse { @@ -18,4 +20,19 @@ export class CipherPermissionsApi extends BaseResponse { static fromJSON(obj: Jsonify) { return Object.assign(new CipherPermissionsApi(), obj); } + + /** + * Converts the SDK CipherPermissionsApi to a CipherPermissionsApi. + */ + static fromSdkCipherPermissions(obj: SdkCipherPermissions): CipherPermissionsApi | undefined { + if (!obj) { + return undefined; + } + + const permissions = new CipherPermissionsApi(); + permissions.delete = obj.delete; + permissions.restore = obj.restore; + + return permissions; + } } diff --git a/libs/common/src/vault/models/data/cipher.data.ts b/libs/common/src/vault/models/data/cipher.data.ts index ee5e5b3e72b..1be70283fb3 100644 --- a/libs/common/src/vault/models/data/cipher.data.ts +++ b/libs/common/src/vault/models/data/cipher.data.ts @@ -39,7 +39,7 @@ export class CipherData { passwordHistory?: PasswordHistoryData[]; collectionIds?: string[]; creationDate: string; - deletedDate: string; + deletedDate: string | null; reprompt: CipherRepromptType; key: string; diff --git a/libs/common/src/vault/models/domain/attachment.spec.ts b/libs/common/src/vault/models/domain/attachment.spec.ts index 40d7ea7f05c..eab67320679 100644 --- a/libs/common/src/vault/models/domain/attachment.spec.ts +++ b/libs/common/src/vault/models/domain/attachment.spec.ts @@ -153,4 +153,21 @@ describe("Attachment", () => { expect(Attachment.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkAttachment", () => { + it("should map to SDK Attachment", () => { + const attachment = new Attachment(data); + + const sdkAttachment = attachment.toSdkAttachment(); + + expect(sdkAttachment).toEqual({ + id: "id", + url: "url", + size: "1100", + sizeName: "1.1 KB", + fileName: "fileName", + key: "key", + }); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/attachment.ts b/libs/common/src/vault/models/domain/attachment.ts index 15ce06e0881..4339f31a2e1 100644 --- a/libs/common/src/vault/models/domain/attachment.ts +++ b/libs/common/src/vault/models/domain/attachment.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Attachment as SdkAttachment } from "@bitwarden/sdk-internal"; + import { Utils } from "../../../platform/misc/utils"; import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; @@ -113,4 +115,20 @@ export class Attachment extends Domain { fileName, }); } + + /** + * Maps to SDK Attachment + * + * @returns {SdkAttachment} - The SDK Attachment object + */ + toSdkAttachment(): SdkAttachment { + return { + id: this.id, + url: this.url, + size: this.size, + sizeName: this.sizeName, + fileName: this.fileName?.toJSON(), + key: this.key?.toJSON(), + }; + } } diff --git a/libs/common/src/vault/models/domain/card.spec.ts b/libs/common/src/vault/models/domain/card.spec.ts index a7011966d94..19546ddcb05 100644 --- a/libs/common/src/vault/models/domain/card.spec.ts +++ b/libs/common/src/vault/models/domain/card.spec.ts @@ -99,4 +99,21 @@ describe("Card", () => { expect(Card.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkCard", () => { + it("should map to SDK Card", () => { + const card = new Card(data); + + const sdkCard = card.toSdkCard(); + + expect(sdkCard).toEqual({ + cardholderName: "encHolder", + brand: "encBrand", + number: "encNumber", + expMonth: "encMonth", + expYear: "encYear", + code: "encCode", + }); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/card.ts b/libs/common/src/vault/models/domain/card.ts index 3d73a8f527c..43068012ef6 100644 --- a/libs/common/src/vault/models/domain/card.ts +++ b/libs/common/src/vault/models/domain/card.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Card as SdkCard } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -85,4 +87,20 @@ export class Card extends Domain { code, }); } + + /** + * Maps Card to SDK format. + * + * @returns {SdkCard} The SDK card object. + */ + toSdkCard(): SdkCard { + return { + cardholderName: this.cardholderName?.toJSON(), + brand: this.brand?.toJSON(), + number: this.number?.toJSON(), + expMonth: this.expMonth?.toJSON(), + expYear: this.expYear?.toJSON(), + code: this.code?.toJSON(), + }; + } } diff --git a/libs/common/src/vault/models/domain/cipher.spec.ts b/libs/common/src/vault/models/domain/cipher.spec.ts index 0ef2233120a..a889f0b969c 100644 --- a/libs/common/src/vault/models/domain/cipher.spec.ts +++ b/libs/common/src/vault/models/domain/cipher.spec.ts @@ -3,6 +3,12 @@ import { Jsonify } from "type-fest"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { KeyService } from "@bitwarden/key-management"; +import { + CipherType as SdkCipherType, + UriMatchType, + CipherRepromptType as SdkCipherRepromptType, + LoginLinkedIdType, +} from "@bitwarden/sdk-internal"; import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec/utils"; import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; @@ -12,7 +18,7 @@ import { ContainerService } from "../../../platform/services/container.service"; import { InitializerKey } from "../../../platform/services/cryptography/initializer-key"; import { UserId } from "../../../types/guid"; import { CipherService } from "../../abstractions/cipher.service"; -import { FieldType, SecureNoteType } from "../../enums"; +import { FieldType, LoginLinkedId, SecureNoteType } from "../../enums"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; import { CipherType } from "../../enums/cipher-type"; import { CipherData } from "../../models/data/cipher.data"; @@ -770,6 +776,165 @@ describe("Cipher DTO", () => { expect(Cipher.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkCipher", () => { + it("should map to SDK Cipher", () => { + const lastUsedDate = new Date("2025-04-15T12:00:00.000Z").getTime(); + const lastLaunched = new Date("2025-04-15T12:00:00.000Z").getTime(); + + const cipherData: CipherData = { + id: "id", + organizationId: "orgId", + folderId: "folderId", + edit: true, + permissions: new CipherPermissionsApi(), + viewPassword: true, + organizationUseTotp: true, + favorite: false, + revisionDate: "2022-01-31T12:00:00.000Z", + type: CipherType.Login, + name: "EncryptedString", + notes: "EncryptedString", + creationDate: "2022-01-01T12:00:00.000Z", + deletedDate: null, + reprompt: CipherRepromptType.None, + key: "EncryptedString", + login: { + uris: [ + { + uri: "EncryptedString", + uriChecksum: "EncryptedString", + match: UriMatchStrategy.Domain, + }, + ], + username: "EncryptedString", + password: "EncryptedString", + passwordRevisionDate: "2022-01-31T12:00:00.000Z", + totp: "EncryptedString", + autofillOnPageLoad: false, + }, + passwordHistory: [ + { password: "EncryptedString", lastUsedDate: "2022-01-31T12:00:00.000Z" }, + ], + attachments: [ + { + id: "a1", + url: "url", + size: "1100", + sizeName: "1.1 KB", + fileName: "file", + key: "EncKey", + }, + { + id: "a2", + url: "url", + size: "1100", + sizeName: "1.1 KB", + fileName: "file", + key: "EncKey", + }, + ], + fields: [ + { + name: "EncryptedString", + value: "EncryptedString", + type: FieldType.Linked, + linkedId: LoginLinkedId.Username, + }, + { + name: "EncryptedString", + value: "EncryptedString", + type: FieldType.Linked, + linkedId: LoginLinkedId.Password, + }, + ], + }; + + const cipher = new Cipher(cipherData, { lastUsedDate, lastLaunched }); + const sdkCipher = cipher.toSdkCipher(); + + expect(sdkCipher).toEqual({ + id: "id", + organizationId: "orgId", + folderId: "folderId", + collectionIds: [], + key: "EncryptedString", + name: "EncryptedString", + notes: "EncryptedString", + type: SdkCipherType.Login, + login: { + username: "EncryptedString", + password: "EncryptedString", + passwordRevisionDate: "2022-01-31T12:00:00.000Z", + uris: [ + { + uri: "EncryptedString", + uriChecksum: "EncryptedString", + match: UriMatchType.Domain, + }, + ], + totp: "EncryptedString", + autofillOnPageLoad: false, + fido2Credentials: undefined, + }, + identity: undefined, + card: undefined, + secureNote: undefined, + sshKey: undefined, + favorite: false, + reprompt: SdkCipherRepromptType.None, + organizationUseTotp: true, + edit: true, + permissions: new CipherPermissionsApi(), + viewPassword: true, + localData: { + lastUsedDate: "2025-04-15T12:00:00.000Z", + lastLaunched: "2025-04-15T12:00:00.000Z", + }, + attachments: [ + { + id: "a1", + url: "url", + size: "1100", + sizeName: "1.1 KB", + fileName: "file", + key: "EncKey", + }, + { + id: "a2", + url: "url", + size: "1100", + sizeName: "1.1 KB", + fileName: "file", + key: "EncKey", + }, + ], + fields: [ + { + name: "EncryptedString", + value: "EncryptedString", + type: FieldType.Linked, + linkedId: LoginLinkedIdType.Username, + }, + { + name: "EncryptedString", + value: "EncryptedString", + type: FieldType.Linked, + linkedId: LoginLinkedIdType.Password, + }, + ], + passwordHistory: [ + { + password: "EncryptedString", + lastUsedDate: "2022-01-31T12:00:00.000Z", + }, + ], + creationDate: "2022-01-01T12:00:00.000Z", + deletedDate: undefined, + revisionDate: "2022-01-31T12:00:00.000Z", + }); + }); + }); }); const mockUserId = "TestUserId" as UserId; diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts index 780690217a8..f647adf198e 100644 --- a/libs/common/src/vault/models/domain/cipher.ts +++ b/libs/common/src/vault/models/domain/cipher.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Cipher as SdkCipher } from "@bitwarden/sdk-internal"; + import { Decryptable } from "../../../platform/interfaces/decryptable.interface"; import { Utils } from "../../../platform/misc/utils"; import Domain from "../../../platform/models/domain/domain-base"; @@ -330,4 +332,72 @@ export class Cipher extends Domain implements Decryptable { return domain; } + + /** + * Maps Cipher to SDK format. + * + * @returns {SdkCipher} The SDK cipher object. + */ + toSdkCipher(): SdkCipher { + const sdkCipher: SdkCipher = { + id: this.id, + organizationId: this.organizationId, + folderId: this.folderId, + collectionIds: this.collectionIds || [], + key: this.key?.toJSON(), + name: this.name.toJSON(), + notes: this.notes?.toJSON(), + type: this.type, + favorite: this.favorite, + organizationUseTotp: this.organizationUseTotp, + edit: this.edit, + permissions: this.permissions, + viewPassword: this.viewPassword, + localData: this.localData + ? { + lastUsedDate: this.localData.lastUsedDate + ? new Date(this.localData.lastUsedDate).toISOString() + : undefined, + lastLaunched: this.localData.lastLaunched + ? new Date(this.localData.lastLaunched).toISOString() + : undefined, + } + : undefined, + attachments: this.attachments?.map((a) => a.toSdkAttachment()), + fields: this.fields?.map((f) => f.toSdkField()), + passwordHistory: this.passwordHistory?.map((ph) => ph.toSdkPasswordHistory()), + revisionDate: this.revisionDate?.toISOString(), + creationDate: this.creationDate?.toISOString(), + deletedDate: this.deletedDate?.toISOString(), + reprompt: this.reprompt, + // Initialize all cipher-type-specific properties as undefined + login: undefined, + identity: undefined, + card: undefined, + secureNote: undefined, + sshKey: undefined, + }; + + switch (this.type) { + case CipherType.Login: + sdkCipher.login = this.login.toSdkLogin(); + break; + case CipherType.SecureNote: + sdkCipher.secureNote = this.secureNote.toSdkSecureNote(); + break; + case CipherType.Card: + sdkCipher.card = this.card.toSdkCard(); + break; + case CipherType.Identity: + sdkCipher.identity = this.identity.toSdkIdentity(); + break; + case CipherType.SshKey: + sdkCipher.sshKey = this.sshKey.toSdkSshKey(); + break; + default: + break; + } + + return sdkCipher; + } } diff --git a/libs/common/src/vault/models/domain/fido2-credential.spec.ts b/libs/common/src/vault/models/domain/fido2-credential.spec.ts index e2cddcea3f3..bde29d0e99c 100644 --- a/libs/common/src/vault/models/domain/fido2-credential.spec.ts +++ b/libs/common/src/vault/models/domain/fido2-credential.spec.ts @@ -167,6 +167,45 @@ describe("Fido2Credential", () => { expect(Fido2Credential.fromJSON(null)).toBeNull(); }); }); + + describe("SDK Fido2Credential Mapping", () => { + it("should map to SDK Fido2Credential", () => { + const data: Fido2CredentialData = { + credentialId: "credentialId", + keyType: "public-key", + keyAlgorithm: "ECDSA", + keyCurve: "P-256", + keyValue: "keyValue", + rpId: "rpId", + userHandle: "userHandle", + userName: "userName", + counter: "2", + rpName: "rpName", + userDisplayName: "userDisplayName", + discoverable: "discoverable", + creationDate: mockDate.toISOString(), + }; + + const credential = new Fido2Credential(data); + const sdkCredential = credential.toSdkFido2Credential(); + + expect(sdkCredential).toEqual({ + credentialId: "credentialId", + keyType: "public-key", + keyAlgorithm: "ECDSA", + keyCurve: "P-256", + keyValue: "keyValue", + rpId: "rpId", + userHandle: "userHandle", + userName: "userName", + counter: "2", + rpName: "rpName", + userDisplayName: "userDisplayName", + discoverable: "discoverable", + creationDate: mockDate.toISOString(), + }); + }); + }); }); function createEncryptedEncString(s: string): EncString { diff --git a/libs/common/src/vault/models/domain/fido2-credential.ts b/libs/common/src/vault/models/domain/fido2-credential.ts index 8b0082892e4..7002a58150d 100644 --- a/libs/common/src/vault/models/domain/fido2-credential.ts +++ b/libs/common/src/vault/models/domain/fido2-credential.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Fido2Credential as SdkFido2Credential } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -148,4 +150,27 @@ export class Fido2Credential extends Domain { creationDate, }); } + + /** + * Maps Fido2Credential to SDK format. + * + * @returns {SdkFido2Credential} The SDK Fido2Credential object. + */ + toSdkFido2Credential(): SdkFido2Credential { + return { + credentialId: this.credentialId?.toJSON(), + keyType: this.keyType.toJSON(), + keyAlgorithm: this.keyAlgorithm.toJSON(), + keyCurve: this.keyCurve.toJSON(), + keyValue: this.keyValue.toJSON(), + rpId: this.rpId.toJSON(), + userHandle: this.userHandle.toJSON(), + userName: this.userName.toJSON(), + counter: this.counter.toJSON(), + rpName: this.rpName?.toJSON(), + userDisplayName: this.userDisplayName?.toJSON(), + discoverable: this.discoverable?.toJSON(), + creationDate: this.creationDate.toISOString(), + }; + } } diff --git a/libs/common/src/vault/models/domain/field.spec.ts b/libs/common/src/vault/models/domain/field.spec.ts index 7dc5556e6cf..c0f9713f7ab 100644 --- a/libs/common/src/vault/models/domain/field.spec.ts +++ b/libs/common/src/vault/models/domain/field.spec.ts @@ -1,6 +1,6 @@ import { mockEnc, mockFromJson } from "../../../../spec"; import { EncryptedString, EncString } from "../../../platform/models/domain/enc-string"; -import { FieldType } from "../../enums"; +import { CardLinkedId, FieldType, IdentityLinkedId, LoginLinkedId } from "../../enums"; import { FieldData } from "../../models/data/field.data"; import { Field } from "../../models/domain/field"; @@ -82,4 +82,26 @@ describe("Field", () => { expect(Field.fromJSON(null)).toBeNull(); }); }); + + describe("SDK Field Mapping", () => { + it("should map to SDK Field", () => { + // Test Login LinkedId + const loginField = new Field(data); + loginField.type = FieldType.Linked; + loginField.linkedId = LoginLinkedId.Username; + expect(loginField.toSdkField().linkedId).toBe(100); + + // Test Card LinkedId + const cardField = new Field(data); + cardField.type = FieldType.Linked; + cardField.linkedId = CardLinkedId.Number; + expect(cardField.toSdkField().linkedId).toBe(305); + + // Test Identity LinkedId + const identityField = new Field(data); + identityField.type = FieldType.Linked; + identityField.linkedId = IdentityLinkedId.LicenseNumber; + expect(identityField.toSdkField().linkedId).toBe(415); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/field.ts b/libs/common/src/vault/models/domain/field.ts index c0f08a38bcc..223c9b39163 100644 --- a/libs/common/src/vault/models/domain/field.ts +++ b/libs/common/src/vault/models/domain/field.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Field as SdkField, LinkedIdType as SdkLinkedIdType } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -73,4 +75,19 @@ export class Field extends Domain { value, }); } + + /** + * Maps Field to SDK format. + * + * @returns {SdkField} The SDK field object. + */ + toSdkField(): SdkField { + return { + name: this.name?.toJSON(), + value: this.value?.toJSON(), + type: this.type, + // Safe type cast: client and SDK LinkedIdType enums have identical values + linkedId: this.linkedId as unknown as SdkLinkedIdType, + }; + } } diff --git a/libs/common/src/vault/models/domain/identity.spec.ts b/libs/common/src/vault/models/domain/identity.spec.ts index 3a95138998b..cf296a6ff08 100644 --- a/libs/common/src/vault/models/domain/identity.spec.ts +++ b/libs/common/src/vault/models/domain/identity.spec.ts @@ -184,4 +184,32 @@ describe("Identity", () => { expect(Identity.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkIdentity", () => { + it("returns the correct SDK Identity object", () => { + const identity = new Identity(data); + const sdkIdentity = identity.toSdkIdentity(); + + expect(sdkIdentity).toEqual({ + title: "enctitle", + firstName: "encfirstName", + middleName: "encmiddleName", + lastName: "enclastName", + address1: "encaddress1", + address2: "encaddress2", + address3: "encaddress3", + city: "enccity", + state: "encstate", + postalCode: "encpostalCode", + country: "enccountry", + company: "enccompany", + email: "encemail", + phone: "encphone", + ssn: "encssn", + username: "encusername", + passportNumber: "encpassportNumber", + licenseNumber: "enclicenseNumber", + }); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/identity.ts b/libs/common/src/vault/models/domain/identity.ts index 5d8c20ef2b3..c7756733a66 100644 --- a/libs/common/src/vault/models/domain/identity.ts +++ b/libs/common/src/vault/models/domain/identity.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Identity as SdkIdentity } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -165,4 +167,32 @@ export class Identity extends Domain { licenseNumber, }); } + + /** + * Maps Identity to SDK format. + * + * @returns {SdkIdentity} The SDK identity object. + */ + toSdkIdentity(): SdkIdentity { + return { + title: this.title?.toJSON(), + firstName: this.firstName?.toJSON(), + middleName: this.middleName?.toJSON(), + lastName: this.lastName?.toJSON(), + address1: this.address1?.toJSON(), + address2: this.address2?.toJSON(), + address3: this.address3?.toJSON(), + city: this.city?.toJSON(), + state: this.state?.toJSON(), + postalCode: this.postalCode?.toJSON(), + country: this.country?.toJSON(), + company: this.company?.toJSON(), + email: this.email?.toJSON(), + phone: this.phone?.toJSON(), + ssn: this.ssn?.toJSON(), + username: this.username?.toJSON(), + passportNumber: this.passportNumber?.toJSON(), + licenseNumber: this.licenseNumber?.toJSON(), + }; + } } diff --git a/libs/common/src/vault/models/domain/login-uri.spec.ts b/libs/common/src/vault/models/domain/login-uri.spec.ts index 6346f38f0de..a0e6b6d7dc9 100644 --- a/libs/common/src/vault/models/domain/login-uri.spec.ts +++ b/libs/common/src/vault/models/domain/login-uri.spec.ts @@ -1,6 +1,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { Jsonify } from "type-fest"; +import { UriMatchType } from "@bitwarden/sdk-internal"; + import { mockEnc, mockFromJson } from "../../../../spec"; import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { UriMatchStrategy } from "../../../models/domain/domain-service"; @@ -118,4 +120,17 @@ describe("LoginUri", () => { expect(LoginUri.fromJSON(null)).toBeNull(); }); }); + + describe("SDK Login Uri Mapping", () => { + it("should map to SDK login uri", () => { + const loginUri = new LoginUri(data); + const sdkLoginUri = loginUri.toSdkLoginUri(); + + expect(sdkLoginUri).toEqual({ + uri: "encUri", + uriChecksum: "encUriChecksum", + match: UriMatchType.Domain, + }); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/login-uri.ts b/libs/common/src/vault/models/domain/login-uri.ts index 883f8c9a616..b3e6fad70dd 100644 --- a/libs/common/src/vault/models/domain/login-uri.ts +++ b/libs/common/src/vault/models/domain/login-uri.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { LoginUri as SdkLoginUri } from "@bitwarden/sdk-internal"; + import { UriMatchStrategySetting } from "../../../models/domain/domain-service"; import { Utils } from "../../../platform/misc/utils"; import Domain from "../../../platform/models/domain/domain-base"; @@ -87,4 +89,17 @@ export class LoginUri extends Domain { uriChecksum, }); } + + /** + * Maps LoginUri to SDK format. + * + * @returns {SdkLoginUri} The SDK login uri object. + */ + toSdkLoginUri(): SdkLoginUri { + return { + uri: this.uri.toJSON(), + uriChecksum: this.uriChecksum.toJSON(), + match: this.match, + }; + } } diff --git a/libs/common/src/vault/models/domain/login.spec.ts b/libs/common/src/vault/models/domain/login.spec.ts index 4f9e4546220..84d12e8131f 100644 --- a/libs/common/src/vault/models/domain/login.spec.ts +++ b/libs/common/src/vault/models/domain/login.spec.ts @@ -202,6 +202,54 @@ describe("Login DTO", () => { expect(Login.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkLogin", () => { + it("should map to SDK login", () => { + const data: LoginData = { + uris: [{ uri: "uri", uriChecksum: "checksum", match: UriMatchStrategy.Domain }], + username: "username", + password: "password", + passwordRevisionDate: "2022-01-31T12:00:00.000Z", + totp: "123", + autofillOnPageLoad: false, + fido2Credentials: [initializeFido2Credential(new Fido2CredentialData())], + }; + const login = new Login(data); + const sdkLogin = login.toSdkLogin(); + + expect(sdkLogin).toEqual({ + username: "username", + password: "password", + passwordRevisionDate: "2022-01-31T12:00:00.000Z", + uris: [ + { + match: 0, + uri: "uri", + uriChecksum: "checksum", + }, + ], + totp: "123", + autofillOnPageLoad: false, + fido2Credentials: [ + { + credentialId: "credentialId", + keyType: "public-key", + keyAlgorithm: "ECDSA", + keyCurve: "P-256", + keyValue: "keyValue", + rpId: "rpId", + userHandle: "userHandle", + userName: "userName", + counter: "counter", + rpName: "rpName", + userDisplayName: "userDisplayName", + discoverable: "discoverable", + creationDate: "2023-01-01T12:00:00.000Z", + }, + ], + }); + }); + }); }); type Fido2CredentialLike = Fido2CredentialData | Fido2CredentialView | Fido2CredentialApi; diff --git a/libs/common/src/vault/models/domain/login.ts b/libs/common/src/vault/models/domain/login.ts index b29b42bf3de..1893212bdaa 100644 --- a/libs/common/src/vault/models/domain/login.ts +++ b/libs/common/src/vault/models/domain/login.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Login as SdkLogin } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -144,4 +146,21 @@ export class Login extends Domain { fido2Credentials, }); } + + /** + * Maps Login to SDK format. + * + * @returns {SdkLogin} The SDK login object. + */ + toSdkLogin(): SdkLogin { + return { + uris: this.uris?.map((u) => u.toSdkLoginUri()), + username: this.username?.toJSON(), + password: this.password?.toJSON(), + passwordRevisionDate: this.passwordRevisionDate?.toISOString(), + totp: this.totp?.toJSON(), + autofillOnPageLoad: this.autofillOnPageLoad, + fido2Credentials: this.fido2Credentials?.map((f) => f.toSdkFido2Credential()), + }; + } } diff --git a/libs/common/src/vault/models/domain/password.spec.ts b/libs/common/src/vault/models/domain/password.spec.ts index 614b9639e52..24163cccf36 100644 --- a/libs/common/src/vault/models/domain/password.spec.ts +++ b/libs/common/src/vault/models/domain/password.spec.ts @@ -70,4 +70,17 @@ describe("Password", () => { expect(Password.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkPasswordHistory", () => { + it("returns the correct SDK PasswordHistory object", () => { + const password = new Password(data); + + const sdkPasswordHistory = password.toSdkPasswordHistory(); + + expect(sdkPasswordHistory).toEqual({ + password: "encPassword", + lastUsedDate: new Date("2022-01-31T12:00:00.000Z").toISOString(), + }); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/password.ts b/libs/common/src/vault/models/domain/password.ts index 8573c224416..b69a61a95a9 100644 --- a/libs/common/src/vault/models/domain/password.ts +++ b/libs/common/src/vault/models/domain/password.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { PasswordHistory } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -57,4 +59,16 @@ export class Password extends Domain { lastUsedDate, }); } + + /** + * Maps Password to SDK format. + * + * @returns {PasswordHistory} The SDK password history object. + */ + toSdkPasswordHistory(): PasswordHistory { + return { + password: this.password.toJSON(), + lastUsedDate: this.lastUsedDate.toISOString(), + }; + } } diff --git a/libs/common/src/vault/models/domain/secure-note.spec.ts b/libs/common/src/vault/models/domain/secure-note.spec.ts index 719cf59f136..ff71e53238d 100644 --- a/libs/common/src/vault/models/domain/secure-note.spec.ts +++ b/libs/common/src/vault/models/domain/secure-note.spec.ts @@ -50,4 +50,17 @@ describe("SecureNote", () => { expect(SecureNote.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkSecureNote", () => { + it("returns the correct SDK SecureNote object", () => { + const secureNote = new SecureNote(); + secureNote.type = SecureNoteType.Generic; + + const sdkSecureNote = secureNote.toSdkSecureNote(); + + expect(sdkSecureNote).toEqual({ + type: 0, + }); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/secure-note.ts b/libs/common/src/vault/models/domain/secure-note.ts index 693ae38d9fb..ac7977b0e46 100644 --- a/libs/common/src/vault/models/domain/secure-note.ts +++ b/libs/common/src/vault/models/domain/secure-note.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { SecureNote as SdkSecureNote } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { SecureNoteType } from "../../enums"; @@ -41,4 +43,15 @@ export class SecureNote extends Domain { return Object.assign(new SecureNote(), obj); } + + /** + * Maps Secure note to SDK format. + * + * @returns {SdkSecureNote} The SDK secure note object. + */ + toSdkSecureNote(): SdkSecureNote { + return { + type: this.type, + }; + } } diff --git a/libs/common/src/vault/models/domain/ssh-key.spec.ts b/libs/common/src/vault/models/domain/ssh-key.spec.ts index f56d738fde8..6576d1a41e9 100644 --- a/libs/common/src/vault/models/domain/ssh-key.spec.ts +++ b/libs/common/src/vault/models/domain/ssh-key.spec.ts @@ -64,4 +64,17 @@ describe("Sshkey", () => { expect(SshKey.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkSshKey", () => { + it("returns the correct SDK SshKey object", () => { + const sshKey = new SshKey(data); + const sdkSshKey = sshKey.toSdkSshKey(); + + expect(sdkSshKey).toEqual({ + privateKey: "privateKey", + publicKey: "publicKey", + fingerprint: "keyFingerprint", + }); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/ssh-key.ts b/libs/common/src/vault/models/domain/ssh-key.ts index f32a1a913ca..96a1c9e58de 100644 --- a/libs/common/src/vault/models/domain/ssh-key.ts +++ b/libs/common/src/vault/models/domain/ssh-key.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { SshKey as SdkSshKey } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -70,4 +72,17 @@ export class SshKey extends Domain { keyFingerprint, }); } + + /** + * Maps SSH key to SDK format. + * + * @returns {SdkSshKey} The SDK SSH key object. + */ + toSdkSshKey(): SdkSshKey { + return { + privateKey: this.privateKey.toJSON(), + publicKey: this.publicKey.toJSON(), + fingerprint: this.keyFingerprint.toJSON(), + }; + } } diff --git a/libs/common/src/vault/models/view/attachment.view.spec.ts b/libs/common/src/vault/models/view/attachment.view.spec.ts index 7cb291f2714..8ae836e1265 100644 --- a/libs/common/src/vault/models/view/attachment.view.spec.ts +++ b/libs/common/src/vault/models/view/attachment.view.spec.ts @@ -1,4 +1,7 @@ +import { AttachmentView as SdkAttachmentView } from "@bitwarden/sdk-internal"; + import { mockFromJson } from "../../../../spec"; +import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { AttachmentView } from "./attachment.view"; @@ -15,4 +18,56 @@ describe("AttachmentView", () => { expect(actual.key).toEqual("encKeyB64_fromJSON"); }); + + describe("fromSdkAttachmentView", () => { + it("should return undefined when the input is null", () => { + const result = AttachmentView.fromSdkAttachmentView(null as unknown as any); + expect(result).toBeUndefined(); + }); + + it("should return an AttachmentView from an SdkAttachmentView", () => { + const sdkAttachmentView = { + id: "id", + url: "url", + size: "size", + sizeName: "sizeName", + fileName: "fileName", + key: "encKeyB64_fromString", + } as SdkAttachmentView; + + const result = AttachmentView.fromSdkAttachmentView(sdkAttachmentView); + + expect(result).toMatchObject({ + id: "id", + url: "url", + size: "size", + sizeName: "sizeName", + fileName: "fileName", + key: null, + encryptedKey: new EncString(sdkAttachmentView.key as string), + }); + }); + }); + + describe("toSdkAttachmentView", () => { + it("should convert AttachmentView to SdkAttachmentView", () => { + const attachmentView = new AttachmentView(); + attachmentView.id = "id"; + attachmentView.url = "url"; + attachmentView.size = "size"; + attachmentView.sizeName = "sizeName"; + attachmentView.fileName = "fileName"; + attachmentView.encryptedKey = new EncString("encKeyB64"); + + const result = attachmentView.toSdkAttachmentView(); + expect(result).toEqual({ + id: "id", + url: "url", + size: "size", + sizeName: "sizeName", + fileName: "fileName", + key: "encKeyB64", + }); + }); + }); }); diff --git a/libs/common/src/vault/models/view/attachment.view.ts b/libs/common/src/vault/models/view/attachment.view.ts index 09839ed2fef..2ef4280d97a 100644 --- a/libs/common/src/vault/models/view/attachment.view.ts +++ b/libs/common/src/vault/models/view/attachment.view.ts @@ -2,7 +2,10 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { AttachmentView as SdkAttachmentView } from "@bitwarden/sdk-internal"; + import { View } from "../../../models/view/view"; +import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { Attachment } from "../domain/attachment"; @@ -13,6 +16,10 @@ export class AttachmentView implements View { sizeName: string = null; fileName: string = null; key: SymmetricCryptoKey = null; + /** + * The SDK returns an encrypted key for the attachment. + */ + encryptedKey: EncString | undefined; constructor(a?: Attachment) { if (!a) { @@ -40,4 +47,37 @@ export class AttachmentView implements View { const key = obj.key == null ? null : SymmetricCryptoKey.fromJSON(obj.key); return Object.assign(new AttachmentView(), obj, { key: key }); } + + /** + * Converts the AttachmentView to a SDK AttachmentView. + */ + toSdkAttachmentView(): SdkAttachmentView { + return { + id: this.id, + url: this.url, + size: this.size, + sizeName: this.sizeName, + fileName: this.fileName, + key: this.encryptedKey?.toJSON(), + }; + } + + /** + * Converts the SDK AttachmentView to a AttachmentView. + */ + static fromSdkAttachmentView(obj: SdkAttachmentView): AttachmentView | undefined { + if (!obj) { + return undefined; + } + + const view = new AttachmentView(); + view.id = obj.id ?? null; + view.url = obj.url ?? null; + view.size = obj.size ?? null; + view.sizeName = obj.sizeName ?? null; + view.fileName = obj.fileName ?? null; + view.encryptedKey = new EncString(obj.key); + + return view; + } } diff --git a/libs/common/src/vault/models/view/card.view.ts b/libs/common/src/vault/models/view/card.view.ts index 9eeb4dabf4d..2adfbb39e89 100644 --- a/libs/common/src/vault/models/view/card.view.ts +++ b/libs/common/src/vault/models/view/card.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { CardView as SdkCardView } from "@bitwarden/sdk-internal"; + import { normalizeExpiryYearFormat } from "../../../autofill/utils"; import { CardLinkedId as LinkedId } from "../../enums"; import { linkedFieldOption } from "../../linked-field-option.decorator"; @@ -146,4 +148,15 @@ export class CardView extends ItemView { return null; } + + /** + * Converts an SDK CardView to a CardView. + */ + static fromSdkCardView(obj: SdkCardView): CardView | undefined { + if (obj == null) { + return undefined; + } + + return Object.assign(new CardView(), obj); + } } diff --git a/libs/common/src/vault/models/view/cipher.view.spec.ts b/libs/common/src/vault/models/view/cipher.view.spec.ts index 3ab2706d356..b9d3e42aa62 100644 --- a/libs/common/src/vault/models/view/cipher.view.spec.ts +++ b/libs/common/src/vault/models/view/cipher.view.spec.ts @@ -1,4 +1,16 @@ -import { mockFromJson } from "../../../../spec"; +import { + CipherView as SdkCipherView, + CipherType as SdkCipherType, + CipherRepromptType as SdkCipherRepromptType, + AttachmentView as SdkAttachmentView, + LoginUriView as SdkLoginUriView, + LoginView as SdkLoginView, + FieldView as SdkFieldView, + FieldType as SdkFieldType, +} from "@bitwarden/sdk-internal"; + +import { mockFromJson, mockFromSdk } from "../../../../spec"; +import { CipherRepromptType } from "../../enums"; import { CipherType } from "../../enums/cipher-type"; import { AttachmentView } from "./attachment.view"; @@ -9,6 +21,7 @@ import { IdentityView } from "./identity.view"; import { LoginView } from "./login.view"; import { PasswordHistoryView } from "./password-history.view"; import { SecureNoteView } from "./secure-note.view"; +import { SshKeyView } from "./ssh-key.view"; jest.mock("../../models/view/login.view"); jest.mock("../../models/view/attachment.view"); @@ -73,4 +86,121 @@ describe("CipherView", () => { expect(actual).toMatchObject(expected); }); }); + + describe("fromSdkCipherView", () => { + let sdkCipherView: SdkCipherView; + + beforeEach(() => { + jest.spyOn(CardView, "fromSdkCardView").mockImplementation(mockFromSdk); + jest.spyOn(IdentityView, "fromSdkIdentityView").mockImplementation(mockFromSdk); + jest.spyOn(LoginView, "fromSdkLoginView").mockImplementation(mockFromSdk); + jest.spyOn(SecureNoteView, "fromSdkSecureNoteView").mockImplementation(mockFromSdk); + jest.spyOn(SshKeyView, "fromSdkSshKeyView").mockImplementation(mockFromSdk); + jest.spyOn(AttachmentView, "fromSdkAttachmentView").mockImplementation(mockFromSdk); + jest.spyOn(FieldView, "fromSdkFieldView").mockImplementation(mockFromSdk); + + sdkCipherView = { + id: "id", + organizationId: "orgId", + folderId: "folderId", + collectionIds: ["collectionId"], + key: undefined, + name: "name", + notes: undefined, + type: SdkCipherType.Login, + favorite: true, + edit: true, + reprompt: SdkCipherRepromptType.None, + organizationUseTotp: false, + viewPassword: true, + localData: undefined, + permissions: undefined, + attachments: [{ id: "attachmentId", url: "attachmentUrl" } as SdkAttachmentView], + login: { + username: "username", + password: "password", + uris: [{ uri: "bitwarden.com" } as SdkLoginUriView], + totp: "totp", + autofillOnPageLoad: true, + } as SdkLoginView, + identity: undefined, + card: undefined, + secureNote: undefined, + sshKey: undefined, + fields: [ + { + name: "fieldName", + value: "fieldValue", + type: SdkFieldType.Linked, + linkedId: 100, + } as SdkFieldView, + ], + passwordHistory: undefined, + creationDate: "2022-01-01T12:00:00.000Z", + revisionDate: "2022-01-02T12:00:00.000Z", + deletedDate: undefined, + }; + }); + + it("returns undefined when input is null", () => { + expect(CipherView.fromSdkCipherView(null as unknown as SdkCipherView)).toBeUndefined(); + }); + + it("maps properties correctly", () => { + const result = CipherView.fromSdkCipherView(sdkCipherView); + + expect(result).toMatchObject({ + id: "id", + organizationId: "orgId", + folderId: "folderId", + collectionIds: ["collectionId"], + name: "name", + notes: null, + type: CipherType.Login, + favorite: true, + edit: true, + reprompt: CipherRepromptType.None, + organizationUseTotp: false, + viewPassword: true, + localData: undefined, + permissions: undefined, + attachments: [ + { + id: "attachmentId", + url: "attachmentUrl", + __fromSdk: true, + }, + ], + login: { + username: "username", + password: "password", + uris: [ + { + uri: "bitwarden.com", + }, + ], + totp: "totp", + autofillOnPageLoad: true, + __fromSdk: true, + }, + identity: new IdentityView(), + card: new CardView(), + secureNote: new SecureNoteView(), + sshKey: new SshKeyView(), + fields: [ + { + name: "fieldName", + value: "fieldValue", + type: SdkFieldType.Linked, + linkedId: 100, + __fromSdk: true, + }, + ], + passwordHistory: null, + creationDate: new Date("2022-01-01T12:00:00.000Z"), + revisionDate: new Date("2022-01-02T12:00:00.000Z"), + deletedDate: null, + }); + }); + }); }); diff --git a/libs/common/src/vault/models/view/cipher.view.ts b/libs/common/src/vault/models/view/cipher.view.ts index 7ddba9e2ed5..1f73903a5bc 100644 --- a/libs/common/src/vault/models/view/cipher.view.ts +++ b/libs/common/src/vault/models/view/cipher.view.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { CipherView as SdkCipherView } from "@bitwarden/sdk-internal"; + import { View } from "../../../models/view/view"; import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface"; import { InitializerKey } from "../../../platform/services/cryptography/initializer-key"; @@ -110,7 +112,7 @@ export class CipherView implements View, InitializerMetadata { get hasOldAttachments(): boolean { if (this.hasAttachments) { for (let i = 0; i < this.attachments.length; i++) { - if (this.attachments[i].key == null) { + if (this.attachments[i].key == null && this.attachments[i].encryptedKey == null) { return true; } } @@ -222,4 +224,68 @@ export class CipherView implements View, InitializerMetadata { return view; } + + /** + * Creates a CipherView from the SDK CipherView. + */ + static fromSdkCipherView(obj: SdkCipherView): CipherView | undefined { + if (obj == null) { + return undefined; + } + + const cipherView = new CipherView(); + cipherView.id = obj.id ?? null; + cipherView.organizationId = obj.organizationId ?? null; + cipherView.folderId = obj.folderId ?? null; + cipherView.name = obj.name; + cipherView.notes = obj.notes ?? null; + cipherView.type = obj.type; + cipherView.favorite = obj.favorite; + cipherView.organizationUseTotp = obj.organizationUseTotp; + cipherView.permissions = CipherPermissionsApi.fromSdkCipherPermissions(obj.permissions); + cipherView.edit = obj.edit; + cipherView.viewPassword = obj.viewPassword; + cipherView.localData = obj.localData + ? { + lastUsedDate: obj.localData.lastUsedDate + ? new Date(obj.localData.lastUsedDate).getTime() + : undefined, + lastLaunched: obj.localData.lastLaunched + ? new Date(obj.localData.lastLaunched).getTime() + : undefined, + } + : undefined; + cipherView.attachments = + obj.attachments?.map((a) => AttachmentView.fromSdkAttachmentView(a)) ?? null; + cipherView.fields = obj.fields?.map((f) => FieldView.fromSdkFieldView(f)) ?? null; + cipherView.passwordHistory = + obj.passwordHistory?.map((ph) => PasswordHistoryView.fromSdkPasswordHistoryView(ph)) ?? null; + cipherView.collectionIds = obj.collectionIds ?? null; + cipherView.revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate); + cipherView.creationDate = obj.creationDate == null ? null : new Date(obj.creationDate); + cipherView.deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate); + cipherView.reprompt = obj.reprompt ?? CipherRepromptType.None; + + switch (obj.type) { + case CipherType.Card: + cipherView.card = CardView.fromSdkCardView(obj.card); + break; + case CipherType.Identity: + cipherView.identity = IdentityView.fromSdkIdentityView(obj.identity); + break; + case CipherType.Login: + cipherView.login = LoginView.fromSdkLoginView(obj.login); + break; + case CipherType.SecureNote: + cipherView.secureNote = SecureNoteView.fromSdkSecureNoteView(obj.secureNote); + break; + case CipherType.SshKey: + cipherView.sshKey = SshKeyView.fromSdkSshKeyView(obj.sshKey); + break; + default: + break; + } + + return cipherView; + } } diff --git a/libs/common/src/vault/models/view/fido2-credential.view.ts b/libs/common/src/vault/models/view/fido2-credential.view.ts index b364d63b8ea..bf1d324d22d 100644 --- a/libs/common/src/vault/models/view/fido2-credential.view.ts +++ b/libs/common/src/vault/models/view/fido2-credential.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Fido2CredentialView as SdkFido2CredentialView } from "@bitwarden/sdk-internal"; + import { ItemView } from "./item.view"; export class Fido2CredentialView extends ItemView { @@ -29,4 +31,29 @@ export class Fido2CredentialView extends ItemView { creationDate, }); } + + /** + * Converts the SDK Fido2CredentialView to a Fido2CredentialView. + */ + static fromSdkFido2CredentialView(obj: SdkFido2CredentialView): Fido2CredentialView | undefined { + if (!obj) { + return undefined; + } + + const view = new Fido2CredentialView(); + view.credentialId = obj.credentialId; + view.keyType = obj.keyType as "public-key"; + view.keyAlgorithm = obj.keyAlgorithm as "ECDSA"; + view.keyCurve = obj.keyCurve as "P-256"; + view.rpId = obj.rpId; + view.userHandle = obj.userHandle; + view.userName = obj.userName; + view.counter = parseInt(obj.counter); + view.rpName = obj.rpName; + view.userDisplayName = obj.userDisplayName; + view.discoverable = obj.discoverable?.toLowerCase() === "true" ? true : false; + view.creationDate = obj.creationDate ? new Date(obj.creationDate) : null; + + return view; + } } diff --git a/libs/common/src/vault/models/view/field.view.ts b/libs/common/src/vault/models/view/field.view.ts index ef8c5113fd0..770150f8a63 100644 --- a/libs/common/src/vault/models/view/field.view.ts +++ b/libs/common/src/vault/models/view/field.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { FieldView as SdkFieldView } from "@bitwarden/sdk-internal"; + import { View } from "../../../models/view/view"; import { FieldType, LinkedIdType } from "../../enums"; import { Field } from "../domain/field"; @@ -31,4 +33,21 @@ export class FieldView implements View { static fromJSON(obj: Partial>): FieldView { return Object.assign(new FieldView(), obj); } + + /** + * Converts the SDK FieldView to a FieldView. + */ + static fromSdkFieldView(obj: SdkFieldView): FieldView | undefined { + if (!obj) { + return undefined; + } + + const view = new FieldView(); + view.name = obj.name; + view.value = obj.value; + view.type = obj.type; + view.linkedId = obj.linkedId as unknown as LinkedIdType; + + return view; + } } diff --git a/libs/common/src/vault/models/view/identity.view.ts b/libs/common/src/vault/models/view/identity.view.ts index 247e5cfec86..a75d11efd95 100644 --- a/libs/common/src/vault/models/view/identity.view.ts +++ b/libs/common/src/vault/models/view/identity.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { IdentityView as SdkIdentityView } from "@bitwarden/sdk-internal"; + import { Utils } from "../../../platform/misc/utils"; import { IdentityLinkedId as LinkedId } from "../../enums"; import { linkedFieldOption } from "../../linked-field-option.decorator"; @@ -158,4 +160,15 @@ export class IdentityView extends ItemView { static fromJSON(obj: Partial>): IdentityView { return Object.assign(new IdentityView(), obj); } + + /** + * Converts the SDK IdentityView to an IdentityView. + */ + static fromSdkIdentityView(obj: SdkIdentityView): IdentityView | undefined { + if (obj == null) { + return undefined; + } + + return Object.assign(new IdentityView(), obj); + } } diff --git a/libs/common/src/vault/models/view/login-uri-view.spec.ts b/libs/common/src/vault/models/view/login-uri-view.spec.ts index efc75096295..155d3d59f7c 100644 --- a/libs/common/src/vault/models/view/login-uri-view.spec.ts +++ b/libs/common/src/vault/models/view/login-uri-view.spec.ts @@ -1,3 +1,5 @@ +import { LoginUriView as SdkLoginUriView, UriMatchType } from "@bitwarden/sdk-internal"; + import { UriMatchStrategy, UriMatchStrategySetting } from "../../../models/domain/domain-service"; import { Utils } from "../../../platform/misc/utils"; @@ -184,6 +186,26 @@ describe("LoginUriView", () => { }); }); }); + + describe("fromSdkLoginUriView", () => { + it("should return undefined when the input is null", () => { + const result = LoginUriView.fromSdkLoginUriView(null as unknown as SdkLoginUriView); + expect(result).toBeUndefined(); + }); + + it("should create a LoginUriView from a SdkLoginUriView", () => { + const sdkLoginUriView = { + uri: "https://example.com", + match: UriMatchType.Host, + } as SdkLoginUriView; + + const loginUriView = LoginUriView.fromSdkLoginUriView(sdkLoginUriView); + + expect(loginUriView).toBeInstanceOf(LoginUriView); + expect(loginUriView!.uri).toBe(sdkLoginUriView.uri); + expect(loginUriView!.match).toBe(sdkLoginUriView.match); + }); + }); }); function uriFactory(match: UriMatchStrategySetting, uri: string) { diff --git a/libs/common/src/vault/models/view/login-uri.view.ts b/libs/common/src/vault/models/view/login-uri.view.ts index 315adb87c75..43d47aa4a3c 100644 --- a/libs/common/src/vault/models/view/login-uri.view.ts +++ b/libs/common/src/vault/models/view/login-uri.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { LoginUriView as SdkLoginUriView } from "@bitwarden/sdk-internal"; + import { UriMatchStrategy, UriMatchStrategySetting } from "../../../models/domain/domain-service"; import { View } from "../../../models/view/view"; import { SafeUrls } from "../../../platform/misc/safe-urls"; @@ -112,6 +114,21 @@ export class LoginUriView implements View { return Object.assign(new LoginUriView(), obj); } + /** + * Converts a LoginUriView object from the SDK to a LoginUriView object. + */ + static fromSdkLoginUriView(obj: SdkLoginUriView): LoginUriView | undefined { + if (obj == null) { + return undefined; + } + + const view = new LoginUriView(); + view.uri = obj.uri; + view.match = obj.match; + + return view; + } + matchesUri( targetUri: string, equivalentDomains: Set, diff --git a/libs/common/src/vault/models/view/login.view.spec.ts b/libs/common/src/vault/models/view/login.view.spec.ts index 728a62deb9d..57e82faf7f1 100644 --- a/libs/common/src/vault/models/view/login.view.spec.ts +++ b/libs/common/src/vault/models/view/login.view.spec.ts @@ -1,4 +1,6 @@ -import { mockFromJson } from "../../../../spec"; +import { LoginView as SdkLoginView } from "@bitwarden/sdk-internal"; + +import { mockFromJson, mockFromSdk } from "../../../../spec"; import { LoginUriView } from "./login-uri.view"; import { LoginView } from "./login.view"; @@ -25,4 +27,35 @@ describe("LoginView", () => { uris: ["uri1_fromJSON", "uri2_fromJSON", "uri3_fromJSON"], }); }); + + describe("fromSdkLoginView", () => { + it("should return undefined when the input is null", () => { + const result = LoginView.fromSdkLoginView(null as unknown as SdkLoginView); + expect(result).toBeUndefined(); + }); + + it("should return a LoginView from an SdkLoginView", () => { + jest.spyOn(LoginUriView, "fromSdkLoginUriView").mockImplementation(mockFromSdk); + + const sdkLoginView = { + username: "username", + password: "password", + passwordRevisionDate: "2025-01-01T01:06:40.441Z", + uris: [{ uri: "bitwarden.com" } as any], + totp: "totp", + autofillOnPageLoad: true, + } as SdkLoginView; + + const result = LoginView.fromSdkLoginView(sdkLoginView); + + expect(result).toMatchObject({ + username: "username", + password: "password", + passwordRevisionDate: new Date("2025-01-01T01:06:40.441Z"), + uris: [expect.objectContaining({ uri: "bitwarden.com", __fromSdk: true })], + totp: "totp", + autofillOnPageLoad: true, + }); + }); + }); }); diff --git a/libs/common/src/vault/models/view/login.view.ts b/libs/common/src/vault/models/view/login.view.ts index 228f3a60c34..41568f643d5 100644 --- a/libs/common/src/vault/models/view/login.view.ts +++ b/libs/common/src/vault/models/view/login.view.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { LoginView as SdkLoginView } from "@bitwarden/sdk-internal"; + import { UriMatchStrategySetting } from "../../../models/domain/domain-service"; import { Utils } from "../../../platform/misc/utils"; import { DeepJsonify } from "../../../types/deep-jsonify"; @@ -100,4 +102,27 @@ export class LoginView extends ItemView { fido2Credentials, }); } + + /** + * Converts the SDK LoginView to a LoginView. + * + * Note: FIDO2 credentials remain encrypted at this stage. + * Unlike other fields that are decrypted as part of the LoginView, the SDK maintains + * the FIDO2 credentials in encrypted form. We can decrypt them later using a separate + * call to client.vault().ciphers().decrypt_fido2_credentials(). + */ + static fromSdkLoginView(obj: SdkLoginView): LoginView | undefined { + if (obj == null) { + return undefined; + } + + const passwordRevisionDate = + obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); + const uris = obj.uris?.map((uri) => LoginUriView.fromSdkLoginUriView(uri)); + + return Object.assign(new LoginView(), obj, { + passwordRevisionDate, + uris, + }); + } } diff --git a/libs/common/src/vault/models/view/password-history.view.spec.ts b/libs/common/src/vault/models/view/password-history.view.spec.ts index 7349e44454d..81894ec7493 100644 --- a/libs/common/src/vault/models/view/password-history.view.spec.ts +++ b/libs/common/src/vault/models/view/password-history.view.spec.ts @@ -1,3 +1,5 @@ +import { PasswordHistoryView as SdkPasswordHistoryView } from "@bitwarden/sdk-internal"; + import { PasswordHistoryView } from "./password-history.view"; describe("PasswordHistoryView", () => { @@ -10,4 +12,25 @@ describe("PasswordHistoryView", () => { expect(actual.lastUsedDate).toEqual(lastUsedDate); }); + + describe("fromSdkPasswordHistoryView", () => { + it("should return undefined when the input is null", () => { + const result = PasswordHistoryView.fromSdkPasswordHistoryView(null as unknown as any); + expect(result).toBeUndefined(); + }); + + it("should return a PasswordHistoryView from an SdkPasswordHistoryView", () => { + const sdkPasswordHistoryView = { + password: "password", + lastUsedDate: "2023-10-01T00:00:00Z", + } as SdkPasswordHistoryView; + + const result = PasswordHistoryView.fromSdkPasswordHistoryView(sdkPasswordHistoryView); + + expect(result).toMatchObject({ + password: "password", + lastUsedDate: new Date("2023-10-01T00:00:00Z"), + }); + }); + }); }); diff --git a/libs/common/src/vault/models/view/password-history.view.ts b/libs/common/src/vault/models/view/password-history.view.ts index 3ab360d5e09..31f05f4cc71 100644 --- a/libs/common/src/vault/models/view/password-history.view.ts +++ b/libs/common/src/vault/models/view/password-history.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { PasswordHistoryView as SdkPasswordHistoryView } from "@bitwarden/sdk-internal"; + import { View } from "../../../models/view/view"; import { Password } from "../domain/password"; @@ -24,4 +26,19 @@ export class PasswordHistoryView implements View { lastUsedDate: lastUsedDate, }); } + + /** + * Converts the SDK PasswordHistoryView to a PasswordHistoryView. + */ + static fromSdkPasswordHistoryView(obj: SdkPasswordHistoryView): PasswordHistoryView | undefined { + if (!obj) { + return undefined; + } + + const view = new PasswordHistoryView(); + view.password = obj.password; + view.lastUsedDate = obj.lastUsedDate == null ? null : new Date(obj.lastUsedDate); + + return view; + } } diff --git a/libs/common/src/vault/models/view/secure-note.view.ts b/libs/common/src/vault/models/view/secure-note.view.ts index c7dd4f8932d..075e4dfc520 100644 --- a/libs/common/src/vault/models/view/secure-note.view.ts +++ b/libs/common/src/vault/models/view/secure-note.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { SecureNoteView as SdkSecureNoteView } from "@bitwarden/sdk-internal"; + import { SecureNoteType } from "../../enums"; import { SecureNote } from "../domain/secure-note"; @@ -26,4 +28,15 @@ export class SecureNoteView extends ItemView { static fromJSON(obj: Partial>): SecureNoteView { return Object.assign(new SecureNoteView(), obj); } + + /** + * Converts the SDK SecureNoteView to a SecureNoteView. + */ + static fromSdkSecureNoteView(obj: SdkSecureNoteView): SecureNoteView | undefined { + if (!obj) { + return undefined; + } + + return Object.assign(new SecureNoteView(), obj); + } } diff --git a/libs/common/src/vault/models/view/ssh-key.view.ts b/libs/common/src/vault/models/view/ssh-key.view.ts index 8f1a9c5a65a..a3d091e4c07 100644 --- a/libs/common/src/vault/models/view/ssh-key.view.ts +++ b/libs/common/src/vault/models/view/ssh-key.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { SshKeyView as SdkSshKeyView } from "@bitwarden/sdk-internal"; + import { SshKey } from "../domain/ssh-key"; import { ItemView } from "./item.view"; @@ -44,4 +46,19 @@ export class SshKeyView extends ItemView { static fromJSON(obj: Partial>): SshKeyView { return Object.assign(new SshKeyView(), obj); } + + /** + * Converts the SDK SshKeyView to a SshKeyView. + */ + static fromSdkSshKeyView(obj: SdkSshKeyView): SshKeyView | undefined { + if (!obj) { + return undefined; + } + + const keyFingerprint = obj.fingerprint; + + return Object.assign(new SshKeyView(), obj, { + keyFingerprint, + }); + } } diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index a8b37e8adc6..b15bc4a9112 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -6,7 +6,7 @@ import { CipherDecryptionKeys, KeyService } from "@bitwarden/key-management"; import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; import { FakeStateProvider } from "../../../spec/fake-state-provider"; -import { makeStaticByteArray } from "../../../spec/utils"; +import { makeStaticByteArray, makeSymmetricCryptoKey } from "../../../spec/utils"; import { ApiService } from "../../abstractions/api.service"; import { SearchService } from "../../abstractions/search.service"; import { AutofillSettingsService } from "../../autofill/services/autofill-settings.service"; @@ -24,6 +24,7 @@ import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypt import { ContainerService } from "../../platform/services/container.service"; import { CipherId, UserId } from "../../types/guid"; import { CipherKey, OrgKey, UserKey } from "../../types/key"; +import { CipherEncryptionService } from "../abstractions/cipher-encryption.service"; import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file-upload.service"; import { FieldType } from "../enums"; import { CipherRepromptType } from "../enums/cipher-reprompt-type"; @@ -34,6 +35,7 @@ import { Cipher } from "../models/domain/cipher"; import { CipherCreateRequest } from "../models/request/cipher-create.request"; import { CipherPartialRequest } from "../models/request/cipher-partial.request"; import { CipherRequest } from "../models/request/cipher.request"; +import { AttachmentView } from "../models/view/attachment.view"; import { CipherView } from "../models/view/cipher.view"; import { LoginUriView } from "../models/view/login-uri.view"; @@ -124,6 +126,7 @@ describe("Cipher Service", () => { accountService = mockAccountServiceWith(mockUserId); const logService = mock(); const stateProvider = new FakeStateProvider(accountService); + const cipherEncryptionService = mock(); const userId = "TestUserId" as UserId; @@ -151,6 +154,7 @@ describe("Cipher Service", () => { stateProvider, accountService, logService, + cipherEncryptionService, ); cipherObj = new Cipher(cipherData); @@ -478,4 +482,85 @@ describe("Cipher Service", () => { ).rejects.toThrow("Cannot rotate ciphers when decryption failures are present"); }); }); + + describe("decrypt", () => { + it("should call decrypt method of CipherEncryptionService when feature flag is true", async () => { + configService.getFeatureFlag.mockResolvedValue(true); + cipherEncryptionService.decrypt.mockResolvedValue(new CipherView(cipherObj)); + + const result = await cipherService.decrypt(cipherObj, userId); + + expect(result).toEqual(new CipherView(cipherObj)); + expect(cipherEncryptionService.decrypt).toHaveBeenCalledWith(cipherObj, userId); + }); + + it("should call legacy decrypt when feature flag is false", async () => { + const mockUserKey = new SymmetricCryptoKey(new Uint8Array(32)) as UserKey; + configService.getFeatureFlag.mockResolvedValue(false); + cipherService.getKeyForCipherKeyDecryption = jest.fn().mockResolvedValue(mockUserKey); + encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32)); + jest.spyOn(cipherObj, "decrypt").mockResolvedValue(new CipherView(cipherObj)); + + const result = await cipherService.decrypt(cipherObj, userId); + + expect(result).toEqual(new CipherView(cipherObj)); + expect(cipherObj.decrypt).toHaveBeenCalledWith(mockUserKey); + }); + }); + + describe("getDecryptedAttachmentBuffer", () => { + const mockEncryptedContent = new Uint8Array([1, 2, 3]); + const mockDecryptedContent = new Uint8Array([4, 5, 6]); + + it("should use SDK when feature flag is enabled", async () => { + const cipher = new Cipher(cipherData); + const attachment = new AttachmentView(cipher.attachments![0]); + configService.getFeatureFlag.mockResolvedValue(true); + + jest.spyOn(cipherService, "ciphers$").mockReturnValue(of({ [cipher.id]: cipherData })); + cipherEncryptionService.decryptAttachmentContent.mockResolvedValue(mockDecryptedContent); + const mockResponse = { + arrayBuffer: jest.fn().mockResolvedValue(mockEncryptedContent.buffer), + } as unknown as Response; + + const result = await cipherService.getDecryptedAttachmentBuffer( + cipher.id as CipherId, + attachment, + mockResponse, + userId, + ); + + expect(result).toEqual(mockDecryptedContent); + expect(cipherEncryptionService.decryptAttachmentContent).toHaveBeenCalledWith( + cipher, + attachment, + mockEncryptedContent, + userId, + ); + }); + + it("should use legacy decryption when feature flag is enabled", async () => { + configService.getFeatureFlag.mockResolvedValue(false); + const cipher = new Cipher(cipherData); + const attachment = new AttachmentView(cipher.attachments![0]); + attachment.key = makeSymmetricCryptoKey(64); + + const mockResponse = { + arrayBuffer: jest.fn().mockResolvedValue(mockEncryptedContent.buffer), + } as unknown as Response; + const mockEncBuf = {} as EncArrayBuffer; + EncArrayBuffer.fromResponse = jest.fn().mockResolvedValue(mockEncBuf); + encryptService.decryptFileData.mockResolvedValue(mockDecryptedContent); + + const result = await cipherService.getDecryptedAttachmentBuffer( + cipher.id as CipherId, + attachment, + mockResponse, + userId, + ); + + expect(result).toEqual(mockDecryptedContent); + expect(encryptService.decryptFileData).toHaveBeenCalledWith(mockEncBuf, attachment.key); + }); + }); }); diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 169568d44e9..6bea56baa5e 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -29,7 +29,8 @@ import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypt import { StateProvider } from "../../platform/state"; import { CipherId, CollectionId, OrganizationId, UserId } from "../../types/guid"; import { OrgKey, UserKey } from "../../types/key"; -import { perUserCache$ } from "../../vault/utils/observable-utilities"; +import { filterOutNullish, perUserCache$ } from "../../vault/utils/observable-utilities"; +import { CipherEncryptionService } from "../abstractions/cipher-encryption.service"; import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service"; import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file-upload.service"; import { FieldType } from "../enums"; @@ -103,6 +104,7 @@ export class CipherService implements CipherServiceAbstraction { private stateProvider: StateProvider, private accountService: AccountService, private logService: LogService, + private cipherEncryptionService: CipherEncryptionService, ) {} localData$(userId: UserId): Observable> { @@ -424,13 +426,21 @@ export class CipherService implements CipherServiceAbstraction { ciphers: Cipher[], userId: UserId, ): Promise<[CipherView[], CipherView[]] | null> { - const keys = await firstValueFrom(this.keyService.cipherDecryptionKeys$(userId, true)); + if (await this.configService.getFeatureFlag(FeatureFlag.PM19941MigrateCipherDomainToSdk)) { + const decryptStartTime = new Date().getTime(); + const decrypted = await this.decryptCiphersWithSdk(ciphers, userId); + this.logService.info( + `[CipherService] Decrypting ${decrypted.length} ciphers took ${new Date().getTime() - decryptStartTime}ms`, + ); + // With SDK, failed ciphers are not returned + return [decrypted, []]; + } + const keys = await firstValueFrom(this.keyService.cipherDecryptionKeys$(userId, true)); if (keys == null || (keys.userKey == null && Object.keys(keys.orgKeys).length === 0)) { // return early if there are no keys to decrypt with return null; } - // Group ciphers by orgId or under 'null' for the user's ciphers const grouped = ciphers.reduce( (agg, c) => { @@ -440,7 +450,6 @@ export class CipherService implements CipherServiceAbstraction { }, {} as Record, ); - const decryptStartTime = new Date().getTime(); const allCipherViews = ( await Promise.all( @@ -464,7 +473,6 @@ export class CipherService implements CipherServiceAbstraction { this.logService.info( `[CipherService] Decrypting ${allCipherViews.length} ciphers took ${new Date().getTime() - decryptStartTime}ms`, ); - // Split ciphers into two arrays, one for successfully decrypted ciphers and one for ciphers that failed to decrypt return allCipherViews.reduce( (acc, c) => { @@ -479,6 +487,21 @@ export class CipherService implements CipherServiceAbstraction { ); } + /** + * Decrypts a cipher using either the SDK or the legacy method based on the feature flag. + * @param cipher The cipher to decrypt. + * @param userId The user ID to use for decryption. + * @returns A promise that resolves to the decrypted cipher view. + */ + async decrypt(cipher: Cipher, userId: UserId): Promise { + if (await this.configService.getFeatureFlag(FeatureFlag.PM19941MigrateCipherDomainToSdk)) { + return await this.cipherEncryptionService.decrypt(cipher, userId); + } else { + const encKey = await this.getKeyForCipherKeyDecryption(cipher, userId); + return await cipher.decrypt(encKey); + } + } + private async reindexCiphers(userId: UserId) { const reindexRequired = this.searchService != null && @@ -895,7 +918,7 @@ export class CipherService implements CipherServiceAbstraction { //then we rollback to using the user key as the main key of encryption of the item //in order to keep item and it's attachments with the same encryption level if (cipher.key != null && !cipherKeyEncryptionEnabled) { - const model = await cipher.decrypt(await this.getKeyForCipherKeyDecryption(cipher, userId)); + const model = await this.decrypt(cipher, userId); cipher = await this.encrypt(model, userId); await this.updateWithServer(cipher); } @@ -1381,6 +1404,43 @@ export class CipherService implements CipherServiceAbstraction { return encryptedCiphers; } + async getDecryptedAttachmentBuffer( + cipherId: CipherId, + attachment: AttachmentView, + response: Response, + userId: UserId, + ): Promise { + const useSdkDecryption = await this.configService.getFeatureFlag( + FeatureFlag.PM19941MigrateCipherDomainToSdk, + ); + + const cipherDomain = await firstValueFrom( + this.ciphers$(userId).pipe(map((ciphersData) => new Cipher(ciphersData[cipherId]))), + ); + + if (useSdkDecryption) { + const encryptedContent = await response.arrayBuffer(); + return this.cipherEncryptionService.decryptAttachmentContent( + cipherDomain, + attachment, + new Uint8Array(encryptedContent), + userId, + ); + } + + const encBuf = await EncArrayBuffer.fromResponse(response); + const key = + attachment.key != null + ? attachment.key + : await firstValueFrom( + this.keyService.orgKeys$(userId).pipe( + filterOutNullish(), + map((orgKeys) => orgKeys[cipherDomain.organizationId as OrganizationId] as OrgKey), + ), + ); + return await this.encryptService.decryptFileData(encBuf, key); + } + /** * @returns a SingleUserState */ @@ -1430,9 +1490,7 @@ export class CipherService implements CipherServiceAbstraction { originalCipher: Cipher, userId: UserId, ): Promise { - const existingCipher = await originalCipher.decrypt( - await this.getKeyForCipherKeyDecryption(originalCipher, userId), - ); + const existingCipher = await this.decrypt(originalCipher, userId); model.passwordHistory = existingCipher.passwordHistory || []; if (model.type === CipherType.Login && existingCipher.type === CipherType.Login) { if ( @@ -1852,4 +1910,17 @@ export class CipherService implements CipherServiceAbstraction { ); return featureEnabled && meetsServerVersion; } + + /** + * Decrypts the provided ciphers using the SDK. + * @param ciphers The ciphers to decrypt. + * @param userId The user ID to use for decryption. + * @returns The decrypted ciphers. + * @private + */ + private async decryptCiphersWithSdk(ciphers: Cipher[], userId: UserId): Promise { + const decryptedViews = await this.cipherEncryptionService.decryptManyLegacy(ciphers, userId); + + return decryptedViews.sort(this.getLocaleSortingFunction()); + } } diff --git a/libs/common/src/vault/services/default-cipher-encryption.service.spec.ts b/libs/common/src/vault/services/default-cipher-encryption.service.spec.ts new file mode 100644 index 00000000000..c0b3d3be85f --- /dev/null +++ b/libs/common/src/vault/services/default-cipher-encryption.service.spec.ts @@ -0,0 +1,334 @@ +import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { + Fido2Credential, + Cipher as SdkCipher, + CipherType as SdkCipherType, + CipherView as SdkCipherView, + CipherListView, + Attachment as SdkAttachment, +} from "@bitwarden/sdk-internal"; + +import { mockEnc } from "../../../spec"; +import { UriMatchStrategy } from "../../models/domain/domain-service"; +import { LogService } from "../../platform/abstractions/log.service"; +import { SdkService } from "../../platform/abstractions/sdk/sdk.service"; +import { UserId } from "../../types/guid"; +import { CipherRepromptType, CipherType } from "../enums"; +import { CipherPermissionsApi } from "../models/api/cipher-permissions.api"; +import { CipherData } from "../models/data/cipher.data"; +import { Cipher } from "../models/domain/cipher"; +import { AttachmentView } from "../models/view/attachment.view"; +import { CipherView } from "../models/view/cipher.view"; +import { Fido2CredentialView } from "../models/view/fido2-credential.view"; + +import { DefaultCipherEncryptionService } from "./default-cipher-encryption.service"; + +const cipherData: CipherData = { + id: "id", + organizationId: "orgId", + folderId: "folderId", + edit: true, + viewPassword: true, + organizationUseTotp: true, + favorite: false, + revisionDate: "2022-01-31T12:00:00.000Z", + type: CipherType.Login, + name: "EncryptedString", + notes: "EncryptedString", + creationDate: "2022-01-01T12:00:00.000Z", + deletedDate: null, + permissions: new CipherPermissionsApi(), + key: "EncKey", + reprompt: CipherRepromptType.None, + login: { + uris: [ + { uri: "EncryptedString", uriChecksum: "EncryptedString", match: UriMatchStrategy.Domain }, + ], + username: "EncryptedString", + password: "EncryptedString", + passwordRevisionDate: "2022-01-31T12:00:00.000Z", + totp: "EncryptedString", + autofillOnPageLoad: false, + }, + passwordHistory: [{ password: "EncryptedString", lastUsedDate: "2022-01-31T12:00:00.000Z" }], + attachments: [ + { + id: "a1", + url: "url", + size: "1100", + sizeName: "1.1 KB", + fileName: "file", + key: "EncKey", + }, + { + id: "a2", + url: "url", + size: "1100", + sizeName: "1.1 KB", + fileName: "file", + key: "EncKey", + }, + ], +}; + +describe("DefaultCipherEncryptionService", () => { + let cipherEncryptionService: DefaultCipherEncryptionService; + const sdkService = mock(); + const logService = mock(); + let sdkCipherView: SdkCipherView; + + const mockSdkClient = { + vault: jest.fn().mockReturnValue({ + ciphers: jest.fn().mockReturnValue({ + decrypt: jest.fn(), + decrypt_list: jest.fn(), + decrypt_fido2_credentials: jest.fn(), + }), + attachments: jest.fn().mockReturnValue({ + decrypt_buffer: jest.fn(), + }), + }), + }; + const mockRef = { + value: mockSdkClient, + [Symbol.dispose]: jest.fn(), + }; + const mockSdk = { + take: jest.fn().mockReturnValue(mockRef), + }; + + const userId = "user-id" as UserId; + + let cipherObj: Cipher; + + beforeEach(() => { + sdkService.userClient$ = jest.fn((userId: UserId) => of(mockSdk)) as any; + cipherEncryptionService = new DefaultCipherEncryptionService(sdkService, logService); + cipherObj = new Cipher(cipherData); + + jest.spyOn(cipherObj, "toSdkCipher").mockImplementation(() => { + return { id: cipherData.id } as SdkCipher; + }); + + sdkCipherView = { + id: "test-id", + type: SdkCipherType.Login, + name: "test-name", + login: { + username: "test-username", + password: "test-password", + }, + } as SdkCipherView; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe("decrypt", () => { + it("should decrypt a cipher successfully", async () => { + const expectedCipherView: CipherView = { + id: "test-id", + type: CipherType.Login, + name: "test-name", + login: { + username: "test-username", + password: "test-password", + }, + } as CipherView; + + mockSdkClient.vault().ciphers().decrypt.mockReturnValue(sdkCipherView); + jest.spyOn(CipherView, "fromSdkCipherView").mockReturnValue(expectedCipherView); + + const result = await cipherEncryptionService.decrypt(cipherObj, userId); + + expect(result).toEqual(expectedCipherView); + expect(cipherObj.toSdkCipher).toHaveBeenCalledTimes(1); + expect(mockSdkClient.vault().ciphers().decrypt).toHaveBeenCalledWith({ id: cipherData.id }); + expect(CipherView.fromSdkCipherView).toHaveBeenCalledWith(sdkCipherView); + expect(mockSdkClient.vault().ciphers().decrypt_fido2_credentials).not.toHaveBeenCalled(); + }); + + it("should decrypt FIDO2 credentials if present", async () => { + const fido2Credentials = [ + { + credentialId: mockEnc("credentialId"), + keyType: mockEnc("keyType"), + keyAlgorithm: mockEnc("keyAlgorithm"), + keyCurve: mockEnc("keyCurve"), + keyValue: mockEnc("keyValue"), + rpId: mockEnc("rpId"), + userHandle: mockEnc("userHandle"), + userName: mockEnc("userName"), + counter: mockEnc("2"), + rpName: mockEnc("rpName"), + userDisplayName: mockEnc("userDisplayName"), + discoverable: mockEnc("true"), + creationDate: new Date("2023-01-01T12:00:00.000Z"), + }, + ] as unknown as Fido2Credential[]; + + sdkCipherView.login!.fido2Credentials = fido2Credentials; + + const expectedCipherView: CipherView = { + id: "test-id", + type: CipherType.Login, + name: "test-name", + login: { + username: "test-username", + password: "test-password", + fido2Credentials: [], + }, + } as unknown as CipherView; + + const fido2CredentialView: Fido2CredentialView = { + credentialId: "credentialId", + keyType: "keyType", + keyAlgorithm: "keyAlgorithm", + keyCurve: "keyCurve", + keyValue: "decrypted-key-value", + rpId: "rpId", + userHandle: "userHandle", + userName: "userName", + counter: 2, + rpName: "rpName", + userDisplayName: "userDisplayName", + discoverable: true, + creationDate: new Date("2023-01-01T12:00:00.000Z"), + } as unknown as Fido2CredentialView; + + mockSdkClient.vault().ciphers().decrypt.mockReturnValue(sdkCipherView); + mockSdkClient.vault().ciphers().decrypt_fido2_credentials.mockReturnValue(fido2Credentials); + mockSdkClient.vault().ciphers().decrypt_fido2_private_key = jest + .fn() + .mockReturnValue("decrypted-key-value"); + + jest.spyOn(CipherView, "fromSdkCipherView").mockReturnValue(expectedCipherView); + jest + .spyOn(Fido2CredentialView, "fromSdkFido2CredentialView") + .mockReturnValueOnce(fido2CredentialView); + + const result = await cipherEncryptionService.decrypt(cipherObj, userId); + + expect(result).toBe(expectedCipherView); + expect(result.login?.fido2Credentials).toEqual([fido2CredentialView]); + expect(mockSdkClient.vault().ciphers().decrypt_fido2_credentials).toHaveBeenCalledWith( + sdkCipherView, + ); + expect(mockSdkClient.vault().ciphers().decrypt_fido2_private_key).toHaveBeenCalledWith( + sdkCipherView, + ); + expect(Fido2CredentialView.fromSdkFido2CredentialView).toHaveBeenCalledTimes(1); + }); + }); + + describe("decryptManyLegacy", () => { + it("should decrypt multiple ciphers successfully", async () => { + const ciphers = [new Cipher(cipherData), new Cipher(cipherData)]; + + const expectedViews = [ + { + id: "test-id-1", + name: "test-name-1", + } as CipherView, + { + id: "test-id-2", + name: "test-name-2", + } as CipherView, + ]; + + mockSdkClient + .vault() + .ciphers() + .decrypt.mockReturnValueOnce({ id: "test-id-1", name: "test-name-1" } as SdkCipherView) + .mockReturnValueOnce({ id: "test-id-2", name: "test-name-2" } as SdkCipherView); + + jest + .spyOn(CipherView, "fromSdkCipherView") + .mockReturnValueOnce(expectedViews[0]) + .mockReturnValueOnce(expectedViews[1]); + + const result = await cipherEncryptionService.decryptManyLegacy(ciphers, userId); + + expect(result).toEqual(expectedViews); + expect(mockSdkClient.vault().ciphers().decrypt).toHaveBeenCalledTimes(2); + expect(CipherView.fromSdkCipherView).toHaveBeenCalledTimes(2); + }); + + it("should throw EmptyError when SDK is not available", async () => { + sdkService.userClient$ = jest.fn().mockReturnValue(of(null)) as any; + + await expect( + cipherEncryptionService.decryptManyLegacy([cipherObj], userId), + ).rejects.toThrow(); + + expect(logService.error).toHaveBeenCalledWith( + expect.stringContaining("Failed to decrypt ciphers"), + ); + }); + }); + + describe("decryptMany", () => { + it("should decrypt multiple ciphers to list views", async () => { + const ciphers = [new Cipher(cipherData), new Cipher(cipherData)]; + + const expectedListViews = [ + { id: "list1", name: "List 1" } as CipherListView, + { id: "list2", name: "List 2" } as CipherListView, + ]; + + mockSdkClient.vault().ciphers().decrypt_list.mockReturnValue(expectedListViews); + + const result = await cipherEncryptionService.decryptMany(ciphers, userId); + + expect(result).toEqual(expectedListViews); + expect(mockSdkClient.vault().ciphers().decrypt_list).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ id: cipherData.id }), + expect.objectContaining({ id: cipherData.id }), + ]), + ); + }); + + it("should throw EmptyError when SDK is not available", async () => { + sdkService.userClient$ = jest.fn().mockReturnValue(of(null)) as any; + + await expect(cipherEncryptionService.decryptMany([cipherObj], userId)).rejects.toThrow(); + + expect(logService.error).toHaveBeenCalledWith( + expect.stringContaining("Failed to decrypt cipher list"), + ); + }); + }); + + describe("decryptAttachmentContent", () => { + it("should decrypt attachment content successfully", async () => { + const cipher = new Cipher(cipherData); + const attachment = new AttachmentView(cipher.attachments![0]); + const encryptedContent = new Uint8Array([1, 2, 3, 4]); + const expectedDecryptedContent = new Uint8Array([5, 6, 7, 8]); + + jest.spyOn(cipher, "toSdkCipher").mockReturnValue({ id: "id" } as SdkCipher); + jest.spyOn(attachment, "toSdkAttachmentView").mockReturnValue({ id: "a1" } as SdkAttachment); + mockSdkClient.vault().attachments().decrypt_buffer.mockReturnValue(expectedDecryptedContent); + + const result = await cipherEncryptionService.decryptAttachmentContent( + cipher, + attachment, + encryptedContent, + userId, + ); + + expect(result).toEqual(expectedDecryptedContent); + expect(cipher.toSdkCipher).toHaveBeenCalled(); + expect(attachment.toSdkAttachmentView).toHaveBeenCalled(); + expect(mockSdkClient.vault().attachments().decrypt_buffer).toHaveBeenCalledWith( + { id: "id" }, + { id: "a1" }, + encryptedContent, + ); + }); + }); +}); diff --git a/libs/common/src/vault/services/default-cipher-encryption.service.ts b/libs/common/src/vault/services/default-cipher-encryption.service.ts new file mode 100644 index 00000000000..2c57df6f5bb --- /dev/null +++ b/libs/common/src/vault/services/default-cipher-encryption.service.ts @@ -0,0 +1,190 @@ +import { EMPTY, catchError, firstValueFrom, map } from "rxjs"; + +import { CipherListView } from "@bitwarden/sdk-internal"; + +import { LogService } from "../../platform/abstractions/log.service"; +import { SdkService } from "../../platform/abstractions/sdk/sdk.service"; +import { UserId } from "../../types/guid"; +import { CipherEncryptionService } from "../abstractions/cipher-encryption.service"; +import { CipherType } from "../enums"; +import { Cipher } from "../models/domain/cipher"; +import { AttachmentView } from "../models/view/attachment.view"; +import { CipherView } from "../models/view/cipher.view"; +import { Fido2CredentialView } from "../models/view/fido2-credential.view"; + +export class DefaultCipherEncryptionService implements CipherEncryptionService { + constructor( + private sdkService: SdkService, + private logService: LogService, + ) {} + + async decrypt(cipher: Cipher, userId: UserId): Promise { + return firstValueFrom( + this.sdkService.userClient$(userId).pipe( + map((sdk) => { + if (!sdk) { + throw new Error("SDK not available"); + } + + using ref = sdk.take(); + const sdkCipherView = ref.value.vault().ciphers().decrypt(cipher.toSdkCipher()); + + const clientCipherView = CipherView.fromSdkCipherView(sdkCipherView)!; + + // Decrypt Fido2 credentials if available + if ( + clientCipherView.type === CipherType.Login && + sdkCipherView.login?.fido2Credentials?.length + ) { + const fido2CredentialViews = ref.value + .vault() + .ciphers() + .decrypt_fido2_credentials(sdkCipherView); + + // TEMPORARY: Manually decrypt the keyValue for Fido2 credentials + // since we don't currently use the SDK for Fido2 Authentication. + const decryptedKeyValue = ref.value + .vault() + .ciphers() + .decrypt_fido2_private_key(sdkCipherView); + + clientCipherView.login.fido2Credentials = fido2CredentialViews + .map((f) => { + const view = Fido2CredentialView.fromSdkFido2CredentialView(f)!; + + return { + ...view, + keyValue: decryptedKeyValue, + }; + }) + .filter((view): view is Fido2CredentialView => view !== undefined); + } + + return clientCipherView; + }), + catchError((error: unknown) => { + this.logService.error(`Failed to decrypt cipher ${error}`); + return EMPTY; + }), + ), + ); + } + + decryptManyLegacy(ciphers: Cipher[], userId: UserId): Promise { + return firstValueFrom( + this.sdkService.userClient$(userId).pipe( + map((sdk) => { + if (!sdk) { + throw new Error("SDK not available"); + } + + using ref = sdk.take(); + + return ciphers.map((cipher) => { + const sdkCipherView = ref.value.vault().ciphers().decrypt(cipher.toSdkCipher()); + const clientCipherView = CipherView.fromSdkCipherView(sdkCipherView)!; + + // Handle FIDO2 credentials if present + if ( + clientCipherView.type === CipherType.Login && + sdkCipherView.login?.fido2Credentials?.length + ) { + const fido2CredentialViews = ref.value + .vault() + .ciphers() + .decrypt_fido2_credentials(sdkCipherView); + + // TODO (PM-21259): Remove manual keyValue decryption for FIDO2 credentials. + // This is a temporary workaround until we can use the SDK for FIDO2 authentication. + const decryptedKeyValue = ref.value + .vault() + .ciphers() + .decrypt_fido2_private_key(sdkCipherView); + + clientCipherView.login.fido2Credentials = fido2CredentialViews + .map((f) => { + const view = Fido2CredentialView.fromSdkFido2CredentialView(f)!; + return { + ...view, + keyValue: decryptedKeyValue, + }; + }) + .filter((view): view is Fido2CredentialView => view !== undefined); + } + + return clientCipherView; + }); + }), + catchError((error: unknown) => { + this.logService.error(`Failed to decrypt ciphers: ${error}`); + return EMPTY; + }), + ), + ); + } + + async decryptMany(ciphers: Cipher[], userId: UserId): Promise { + return firstValueFrom( + this.sdkService.userClient$(userId).pipe( + map((sdk) => { + if (!sdk) { + throw new Error("SDK is undefined"); + } + + using ref = sdk.take(); + + return ref.value + .vault() + .ciphers() + .decrypt_list(ciphers.map((cipher) => cipher.toSdkCipher())); + }), + catchError((error: unknown) => { + this.logService.error(`Failed to decrypt cipher list: ${error}`); + return EMPTY; + }), + ), + ); + } + + /** + * Decrypts an attachment's content from a response object. + * + * @param cipher The encrypted cipher object that owns the attachment + * @param attachment The encrypted attachment object + * @param encryptedContent The encrypted content as a Uint8Array + * @param userId The user ID whose key will be used for decryption + * + * @returns A promise that resolves to the decrypted content + */ + async decryptAttachmentContent( + cipher: Cipher, + attachment: AttachmentView, + encryptedContent: Uint8Array, + userId: UserId, + ): Promise { + return firstValueFrom( + this.sdkService.userClient$(userId).pipe( + map((sdk) => { + if (!sdk) { + throw new Error("SDK is undefined"); + } + + using ref = sdk.take(); + + return ref.value + .vault() + .attachments() + .decrypt_buffer( + cipher.toSdkCipher(), + attachment.toSdkAttachmentView(), + encryptedContent, + ); + }), + catchError((error: unknown) => { + this.logService.error(`Failed to decrypt cipher buffer: ${error}`); + return EMPTY; + }), + ), + ); + } +} diff --git a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts index 9284718a063..af29d8263c6 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts @@ -118,9 +118,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); - const view = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const view = await this.cipherService.decrypt(cipher, activeUserId); this.cleanupCipher(view); this.result.ciphers.push(view); } diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts index ae408af421b..6ed4caa3f8d 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts @@ -10,7 +10,7 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { UserId } from "@bitwarden/common/types/guid"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -172,6 +172,8 @@ describe("VaultExportService", () => { let apiService: MockProxy; let fetchMock: jest.Mock; + const userId = "" as UserId; + beforeEach(() => { cryptoFunctionService = mock(); cipherService = mock(); @@ -185,7 +187,6 @@ describe("VaultExportService", () => { keyService.userKey$.mockReturnValue(new BehaviorSubject("mockOriginalUserKey" as any)); - const userId = "" as UserId; const accountInfo: AccountInfo = { email: "", emailVerified: true, @@ -338,7 +339,9 @@ describe("VaultExportService", () => { cipherService.getAllDecrypted.mockResolvedValue([cipherView]); folderService.getAllDecryptedFromState.mockResolvedValue([]); - encryptService.decryptFileData.mockResolvedValue(new Uint8Array(255)); + cipherService.getDecryptedAttachmentBuffer.mockRejectedValue( + new Error("Error decrypting attachment"), + ); global.fetch = jest.fn(() => Promise.resolve({ @@ -356,13 +359,17 @@ describe("VaultExportService", () => { it("contains attachments with folders", async () => { const cipherData = new CipherData(); cipherData.id = "mock-id"; + const cipherRecord: Record = { + ["mock-id" as CipherId]: cipherData, + }; const cipherView = new CipherView(new Cipher(cipherData)); const attachmentView = new AttachmentView(new Attachment(new AttachmentData())); attachmentView.fileName = "mock-file-name"; cipherView.attachments = [attachmentView]; + cipherService.ciphers$.mockReturnValue(of(cipherRecord)); cipherService.getAllDecrypted.mockResolvedValue([cipherView]); folderService.getAllDecryptedFromState.mockResolvedValue([]); - encryptService.decryptFileData.mockResolvedValue(new Uint8Array(255)); + cipherService.getDecryptedAttachmentBuffer.mockResolvedValue(new Uint8Array(255)); global.fetch = jest.fn(() => Promise.resolve({ status: 200, diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts index 8b66580d4cd..537585aac7e 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts @@ -12,14 +12,12 @@ import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/a import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { CipherWithIdExport, FolderWithIdExport } from "@bitwarden/common/models/export"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { UserId } from "@bitwarden/common/types/guid"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { Folder } from "@bitwarden/common/vault/models/domain/folder"; -import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { KdfConfigService, KeyService } from "@bitwarden/key-management"; @@ -118,8 +116,19 @@ export class IndividualVaultExportService const cipherFolder = attachmentsFolder.folder(cipher.id); for (const attachment of cipher.attachments) { const response = await this.downloadAttachment(cipher.id, attachment.id); - const decBuf = await this.decryptAttachment(cipher, attachment, response); - cipherFolder.file(attachment.fileName, decBuf); + + try { + const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( + cipher.id as CipherId, + attachment, + response, + activeUserId, + ); + + cipherFolder.file(attachment.fileName, decBuf); + } catch { + throw new Error("Error decrypting attachment"); + } } } @@ -146,23 +155,6 @@ export class IndividualVaultExportService return response; } - private async decryptAttachment( - cipher: CipherView, - attachment: AttachmentView, - response: Response, - ) { - try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = - attachment.key != null - ? attachment.key - : await this.keyService.getOrgKey(cipher.organizationId); - return await this.encryptService.decryptFileData(encBuf, key); - } catch { - throw new Error("Error decrypting attachment"); - } - } - private async getDecryptedExport( activeUserId: UserId, format: "json" | "csv", diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts index fc46915c15d..4f30f299062 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts @@ -155,12 +155,9 @@ export class OrganizationVaultExportService .forEach(async (c) => { const cipher = new Cipher(new CipherData(c)); exportPromises.push( - this.cipherService - .getKeyForCipherKeyDecryption(cipher, activeUserId) - .then((key) => cipher.decrypt(key)) - .then((decCipher) => { - decCiphers.push(decCipher); - }), + this.cipherService.decrypt(cipher, activeUserId).then((decCipher) => { + decCiphers.push(decCipher); + }), ); }); } diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts index 0fe358cd89b..da827addf67 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts @@ -80,6 +80,7 @@ describe("CipherAttachmentsComponent", () => { get: cipherServiceGet, saveAttachmentWithServer, getKeyForCipherKeyDecryption: () => Promise.resolve(null), + decrypt: jest.fn().mockResolvedValue(cipherView), }, }, { diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts index 29a80c826c6..aa9769ec392 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts @@ -137,9 +137,7 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { this.organization = await this.getOrganization(); this.cipherDomain = await this.getCipher(this.cipherId); - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, this.activeUserId); // Update the initial state of the submit button if (this.submitBtn) { @@ -210,9 +208,7 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { ); // re-decrypt the cipher to update the attachments - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, this.activeUserId); // Reset reactive form and input element this.fileInput.nativeElement.value = ""; diff --git a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts index 98286e4bbb2..68eac4f0da2 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts @@ -3,7 +3,6 @@ import { inject, Injectable } from "@angular/core"; import { firstValueFrom } from "rxjs"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -21,13 +20,10 @@ function isSetEqual(a: Set, b: Set) { export class DefaultCipherFormService implements CipherFormService { private cipherService: CipherService = inject(CipherService); private accountService: AccountService = inject(AccountService); - private apiService: ApiService = inject(ApiService); async decryptCipher(cipher: Cipher): Promise { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - return await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + return await this.cipherService.decrypt(cipher, activeUserId); } async saveCipher(cipher: CipherView, config: CipherFormConfig): Promise { @@ -46,9 +42,7 @@ export class DefaultCipherFormService implements CipherFormService { // Creating a new cipher if (cipher.id == null) { savedCipher = await this.cipherService.createWithServer(encryptedCipher, config.admin); - return await savedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(savedCipher, activeUserId), - ); + return await this.cipherService.decrypt(savedCipher, activeUserId); } if (config.originalCipher == null) { @@ -100,8 +94,6 @@ export class DefaultCipherFormService implements CipherFormService { return null; } - return await savedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(savedCipher, activeUserId), - ); + return await this.cipherService.decrypt(savedCipher, activeUserId); } } diff --git a/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts b/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts index f621ca63101..8a4e962707d 100644 --- a/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts @@ -6,15 +6,16 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { StateProvider } from "@bitwarden/common/platform/state"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; import { PasswordRepromptService } from "../../services/password-reprompt.service"; @@ -51,6 +52,21 @@ describe("DownloadAttachmentComponent", () => { }, } as CipherView; + const ciphers$ = new BehaviorSubject({ + "5555-444-3333": { + id: "5555-444-3333", + attachments: [ + { + id: "222-3333-4444", + fileName: "encrypted-filename", + key: "encrypted-key", + }, + ], + }, + }); + + const getFeatureFlag = jest.fn().mockResolvedValue(false); + beforeEach(async () => { showToast.mockClear(); getAttachmentData.mockClear(); @@ -60,13 +76,22 @@ describe("DownloadAttachmentComponent", () => { imports: [DownloadAttachmentComponent], providers: [ { provide: EncryptService, useValue: mock() }, - { provide: KeyService, useValue: mock() }, { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: StateProvider, useValue: { activeUserId$ } }, { provide: ToastService, useValue: { showToast } }, { provide: ApiService, useValue: { getAttachmentData } }, { provide: FileDownloadService, useValue: { download } }, { provide: PasswordRepromptService, useValue: mock() }, + { + provide: ConfigService, + useValue: { + getFeatureFlag, + }, + }, + { + provide: CipherService, + useValue: { ciphers$: () => ciphers$, getDecryptedAttachmentBuffer: jest.fn() }, + }, ], }).compileComponents(); }); @@ -128,10 +153,12 @@ describe("DownloadAttachmentComponent", () => { }); }); - it("shows an error toast when EncArrayBuffer fails", async () => { + it("shows an error toast when getDecryptedAttachmentBuffer fails", async () => { getAttachmentData.mockResolvedValue({ url: "https://www.downloadattachement.com" }); fetchMock.mockResolvedValue({ status: 200 }); - EncArrayBuffer.fromResponse = jest.fn().mockRejectedValue({}); + + const cipherService = TestBed.inject(CipherService) as jest.Mocked; + cipherService.getDecryptedAttachmentBuffer.mockRejectedValue(new Error()); await component.download(); diff --git a/libs/vault/src/components/download-attachment/download-attachment.component.ts b/libs/vault/src/components/download-attachment/download-attachment.component.ts index e64777ebb8e..f06d6db582a 100644 --- a/libs/vault/src/components/download-attachment/download-attachment.component.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.ts @@ -2,23 +2,19 @@ // @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { NEVER, switchMap } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { StateProvider } from "@bitwarden/common/platform/state"; -import { EmergencyAccessId, OrganizationId } from "@bitwarden/common/types/guid"; -import { OrgKey } from "@bitwarden/common/types/key"; +import { CipherId, EmergencyAccessId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { AsyncActionsModule, IconButtonModule, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; @Component({ standalone: true, @@ -42,29 +38,14 @@ export class DownloadAttachmentComponent { /** When owners/admins can mange all items and when accessing from the admin console, use the admin endpoint */ @Input() admin?: boolean = false; - /** The organization key if the cipher is associated with one */ - private orgKey: OrgKey | null = null; - constructor( private i18nService: I18nService, private apiService: ApiService, private fileDownloadService: FileDownloadService, private toastService: ToastService, - private encryptService: EncryptService, private stateProvider: StateProvider, - private keyService: KeyService, - ) { - this.stateProvider.activeUserId$ - .pipe( - switchMap((userId) => (userId !== null ? this.keyService.orgKeys$(userId) : NEVER)), - takeUntilDestroyed(), - ) - .subscribe((data: Record | null) => { - if (data) { - this.orgKey = data[this.cipher.organizationId as OrganizationId]; - } - }); - } + private cipherService: CipherService, + ) {} /** Download the attachment */ download = async () => { @@ -100,9 +81,15 @@ export class DownloadAttachmentComponent { } try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = this.attachment.key != null ? this.attachment.key : this.orgKey; - const decBuf = await this.encryptService.decryptFileData(encBuf, key); + const userId = await firstValueFrom(this.stateProvider.activeUserId$); + + const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( + this.cipher.id as CipherId, + this.attachment, + response, + userId, + ); + this.fileDownloadService.download({ fileName: this.attachment.fileName, blobData: decBuf, From 1a1481bbd643169a554e72437c3f8b78264cfbbc Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Wed, 14 May 2025 16:01:03 +0100 Subject: [PATCH 053/245] Resolve the tiny line issue (#14758) --- .../billing/members/free-bitwarden-families.component.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/billing/members/free-bitwarden-families.component.html b/apps/web/src/app/billing/members/free-bitwarden-families.component.html index ee21909beec..243cf612c73 100644 --- a/apps/web/src/app/billing/members/free-bitwarden-families.component.html +++ b/apps/web/src/app/billing/members/free-bitwarden-families.component.html @@ -67,7 +67,9 @@ {{ "resendInvitation" | i18n }} -
+ +
+
diff --git a/libs/components/src/form-field/form-field.component.ts b/libs/components/src/form-field/form-field.component.ts index 4f7c2a67483..e810aaec8cb 100644 --- a/libs/components/src/form-field/form-field.component.ts +++ b/libs/components/src/form-field/form-field.component.ts @@ -91,7 +91,7 @@ export class BitFormFieldComponent implements AfterContentChecked { protected defaultContentIsFocused = signal(false); @HostListener("focusin", ["$event.target"]) onFocusIn(target: HTMLElement) { - this.defaultContentIsFocused.set(target.matches(".default-content *:focus-visible")); + this.defaultContentIsFocused.set(target.matches("[data-default-content] *:focus-visible")); } @HostListener("focusout") onFocusOut() { diff --git a/libs/components/src/item/item-content.component.ts b/libs/components/src/item/item-content.component.ts index 2a6e06291fd..76fa3996210 100644 --- a/libs/components/src/item/item-content.component.ts +++ b/libs/components/src/item/item-content.component.ts @@ -25,7 +25,8 @@ import { TypographyModule } from "../typography"; * y-axis padding should be kept in sync with `item-action.component.ts`'s `top` and `bottom` units. * we want this to be the same height as the `item-action`'s `:after` element */ - "fvw-target tw-outline-none tw-text-main hover:tw-text-main tw-no-underline hover:tw-no-underline tw-text-base tw-py-2 tw-px-4 bit-compact:tw-py-1.5 bit-compact:tw-px-2 tw-bg-transparent tw-w-full tw-border-none tw-flex tw-gap-4 tw-items-center tw-justify-between", + "tw-outline-none tw-text-main hover:tw-text-main tw-no-underline hover:tw-no-underline tw-text-base tw-py-2 tw-px-4 bit-compact:tw-py-1.5 bit-compact:tw-px-2 tw-bg-transparent tw-w-full tw-border-none tw-flex tw-gap-4 tw-items-center tw-justify-between", + "data-fvw-target": "", }, changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/libs/components/src/item/item.component.html b/libs/components/src/item/item.component.html index 812d6b0315b..2863bb2891b 100644 --- a/libs/components/src/item/item.component.html +++ b/libs/components/src/item/item.component.html @@ -1,4 +1,4 @@ - + diff --git a/libs/components/src/item/item.component.ts b/libs/components/src/item/item.component.ts index 1ef4a4af1fa..0c45f98139e 100644 --- a/libs/components/src/item/item.component.ts +++ b/libs/components/src/item/item.component.ts @@ -19,7 +19,7 @@ import { ItemActionComponent } from "./item-action.component"; providers: [{ provide: A11yRowDirective, useExisting: ItemComponent }], host: { class: - "tw-block tw-box-border tw-overflow-hidden tw-flex tw-bg-background [&:has(.item-main-content_button:hover,.item-main-content_a:hover)]:tw-cursor-pointer [&:has(.item-main-content_button:hover,.item-main-content_a:hover)]:tw-bg-primary-100 tw-text-main tw-border-solid tw-border-b tw-border-0 [&:not(bit-layout_*)]:tw-rounded-lg bit-compact:[&:not(bit-layout_*)]:tw-rounded-none bit-compact:[&:not(bit-layout_*)]:last-of-type:tw-rounded-b-lg bit-compact:[&:not(bit-layout_*)]:first-of-type:tw-rounded-t-lg tw-min-h-9 tw-mb-1.5 bit-compact:tw-mb-0", + "tw-block tw-box-border tw-overflow-hidden tw-flex tw-bg-background [&:has([data-item-main-content]_button:hover,[data-item-main-content]_a:hover)]:tw-cursor-pointer [&:has([data-item-main-content]_button:hover,[data-item-main-content]_a:hover)]:tw-bg-primary-100 tw-text-main tw-border-solid tw-border-b tw-border-0 [&:not(bit-layout_*)]:tw-rounded-lg bit-compact:[&:not(bit-layout_*)]:tw-rounded-none bit-compact:[&:not(bit-layout_*)]:last-of-type:tw-rounded-b-lg bit-compact:[&:not(bit-layout_*)]:first-of-type:tw-rounded-t-lg tw-min-h-9 tw-mb-1.5 bit-compact:tw-mb-0", }, }) export class ItemComponent extends A11yRowDirective { @@ -29,7 +29,7 @@ export class ItemComponent extends A11yRowDirective { protected focusVisibleWithin = signal(false); @HostListener("focusin", ["$event.target"]) onFocusIn(target: HTMLElement) { - this.focusVisibleWithin.set(target.matches(".fvw-target:focus-visible")); + this.focusVisibleWithin.set(target.matches("[data-fvw-target]:focus-visible")); } @HostListener("focusout") onFocusOut() { diff --git a/libs/components/src/navigation/nav-item.component.html b/libs/components/src/navigation/nav-item.component.html index c3cf559389f..595ddf5a99f 100644 --- a/libs/components/src/navigation/nav-item.component.html +++ b/libs/components/src/navigation/nav-item.component.html @@ -70,10 +70,11 @@ - + `, + template: ` + + + + + `, }) class StoryDialogComponent { constructor(public dialogService: DialogService) {} @@ -31,6 +41,14 @@ class StoryDialogComponent { }, }); } + + openDrawer() { + this.dialogService.openDrawer(StoryDialogContentComponent, { + data: { + animal: "panda", + }, + }); + } } @Component({ @@ -65,25 +83,37 @@ export default { title: "Component Library/Dialogs/Service", component: StoryDialogComponent, decorators: [ + positionFixedWrapperDecorator(), moduleMetadata({ declarations: [StoryDialogContentComponent], imports: [ SharedModule, ButtonModule, + NoopAnimationsModule, DialogModule, IconButtonModule, DialogCloseDirective, DialogComponent, DialogTitleContainerDirective, + RouterTestingModule, + LayoutComponent, ], + providers: [DialogService], + }), + applicationConfig({ providers: [ - DialogService, { provide: I18nService, useFactory: () => { return new I18nMockService({ close: "Close", - loading: "Loading", + search: "Search", + skipToContent: "Skip to content", + submenu: "submenu", + toggleCollapse: "toggle collapse", + toggleSideNavigation: "Toggle side navigation", + yes: "Yes", + no: "No", }); }, }, @@ -100,4 +130,21 @@ export default { type Story = StoryObj; -export const Default: Story = {}; +export const Default: Story = { + play: async (context) => { + const canvas = context.canvasElement; + + const button = getAllByRole(canvas, "button")[0]; + await userEvent.click(button); + }, +}; + +/** Drawers must be a descendant of `bit-layout`. */ +export const Drawer: Story = { + play: async (context) => { + const canvas = context.canvasElement; + + const button = getAllByRole(canvas, "button")[1]; + await userEvent.click(button); + }, +}; diff --git a/libs/components/src/dialog/dialog.service.ts b/libs/components/src/dialog/dialog.service.ts index 83aaaff470e..409bf0a5b55 100644 --- a/libs/components/src/dialog/dialog.service.ts +++ b/libs/components/src/dialog/dialog.service.ts @@ -1,31 +1,25 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { - DEFAULT_DIALOG_CONFIG, - Dialog, - DialogConfig, - DialogRef, - DIALOG_SCROLL_STRATEGY, + Dialog as CdkDialog, + DialogConfig as CdkDialogConfig, + DialogRef as CdkDialogRefBase, + DIALOG_DATA, + DialogCloseOptions, } from "@angular/cdk/dialog"; -import { ComponentType, Overlay, OverlayContainer, ScrollStrategy } from "@angular/cdk/overlay"; -import { - Inject, - Injectable, - Injector, - OnDestroy, - Optional, - SkipSelf, - TemplateRef, -} from "@angular/core"; +import { ComponentType, ScrollStrategy } from "@angular/cdk/overlay"; +import { ComponentPortal, Portal } from "@angular/cdk/portal"; +import { Injectable, Injector, TemplateRef, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { NavigationEnd, Router } from "@angular/router"; -import { filter, firstValueFrom, Subject, switchMap, takeUntil } from "rxjs"; +import { filter, firstValueFrom, map, Observable, Subject, switchMap } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DrawerService } from "../drawer/drawer.service"; + import { SimpleConfigurableDialogComponent } from "./simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component"; -import { SimpleDialogOptions, Translation } from "./simple-dialog/types"; +import { SimpleDialogOptions } from "./simple-dialog/types"; /** * The default `BlockScrollStrategy` does not work well with virtual scrolling. @@ -48,61 +42,163 @@ class CustomBlockScrollStrategy implements ScrollStrategy { detach() {} } +export abstract class DialogRef + implements Pick, "close" | "closed" | "disableClose" | "componentInstance"> +{ + abstract readonly isDrawer?: boolean; + + // --- From CdkDialogRef --- + abstract close(result?: R, options?: DialogCloseOptions): void; + abstract readonly closed: Observable; + abstract disableClose: boolean | undefined; + /** + * @deprecated + * Does not work with drawer dialogs. + **/ + abstract componentInstance: C | null; +} + +export type DialogConfig = Pick< + CdkDialogConfig, + "data" | "disableClose" | "ariaModal" | "positionStrategy" | "height" | "width" +>; + +class DrawerDialogRef implements DialogRef { + readonly isDrawer = true; + + private _closed = new Subject(); + closed = this._closed.asObservable(); + disableClose = false; + + /** The portal containing the drawer */ + portal?: Portal; + + constructor(private drawerService: DrawerService) {} + + close(result?: R, _options?: DialogCloseOptions): void { + if (this.disableClose) { + return; + } + this.drawerService.close(this.portal!); + this._closed.next(result); + this._closed.complete(); + } + + componentInstance: C | null = null; +} + +/** + * DialogRef that delegates functionality to the CDK implementation + **/ +export class CdkDialogRef implements DialogRef { + readonly isDrawer = false; + + /** This is not available until after construction, @see DialogService.open. */ + cdkDialogRefBase!: CdkDialogRefBase; + + // --- Delegated to CdkDialogRefBase --- + + close(result?: R, options?: DialogCloseOptions): void { + this.cdkDialogRefBase.close(result, options); + } + + get closed(): Observable { + return this.cdkDialogRefBase.closed; + } + + get disableClose(): boolean | undefined { + return this.cdkDialogRefBase.disableClose; + } + set disableClose(value: boolean | undefined) { + this.cdkDialogRefBase.disableClose = value; + } + + // Delegate the `componentInstance` property to the CDK DialogRef + get componentInstance(): C | null { + return this.cdkDialogRefBase.componentInstance; + } +} + @Injectable() -export class DialogService extends Dialog implements OnDestroy { - private _destroy$ = new Subject(); +export class DialogService { + private dialog = inject(CdkDialog); + private drawerService = inject(DrawerService); + private injector = inject(Injector); + private router = inject(Router, { optional: true }); + private authService = inject(AuthService, { optional: true }); + private i18nService = inject(I18nService); private backDropClasses = ["tw-fixed", "tw-bg-black", "tw-bg-opacity-30", "tw-inset-0"]; - private defaultScrollStrategy = new CustomBlockScrollStrategy(); + private activeDrawer: DrawerDialogRef | null = null; - constructor( - /** Parent class constructor */ - _overlay: Overlay, - _injector: Injector, - @Optional() @Inject(DEFAULT_DIALOG_CONFIG) _defaultOptions: DialogConfig, - @Optional() @SkipSelf() _parentDialog: Dialog, - _overlayContainer: OverlayContainer, - @Inject(DIALOG_SCROLL_STRATEGY) scrollStrategy: any, - - /** Not in parent class */ - @Optional() router: Router, - @Optional() authService: AuthService, - - protected i18nService: I18nService, - ) { - super(_overlay, _injector, _defaultOptions, _parentDialog, _overlayContainer, scrollStrategy); - + constructor() { + /** + * TODO: This logic should exist outside of `libs/components`. + * @see https://bitwarden.atlassian.net/browse/CL-657 + **/ /** Close all open dialogs if the vault locks */ - if (router && authService) { - router.events + if (this.router && this.authService) { + this.router.events .pipe( filter((event) => event instanceof NavigationEnd), - switchMap(() => authService.getAuthStatus()), + switchMap(() => this.authService!.getAuthStatus()), filter((v) => v !== AuthenticationStatus.Unlocked), - takeUntil(this._destroy$), + takeUntilDestroyed(), ) .subscribe(() => this.closeAll()); } } - override ngOnDestroy(): void { - this._destroy$.next(); - this._destroy$.complete(); - super.ngOnDestroy(); - } - - override open( + open( componentOrTemplateRef: ComponentType | TemplateRef, config?: DialogConfig>, ): DialogRef { - config = { + /** + * This is a bit circular in nature: + * We need the DialogRef instance for the DI injector that is passed *to* `Dialog.open`, + * but we get the base CDK DialogRef instance *from* `Dialog.open`. + * + * To break the circle, we define CDKDialogRef as a wrapper for the CDKDialogRefBase. + * This allows us to create the class instance and provide the base instance later, almost like "deferred inheritance". + **/ + const ref = new CdkDialogRef(); + const injector = this.createInjector({ + data: config?.data, + dialogRef: ref, + }); + + // Merge the custom config with the default config + const _config = { backdropClass: this.backDropClasses, scrollStrategy: this.defaultScrollStrategy, + injector, ...config, }; - return super.open(componentOrTemplateRef, config); + ref.cdkDialogRefBase = this.dialog.open(componentOrTemplateRef, _config); + return ref; + } + + /** Opens a dialog in the side drawer */ + openDrawer( + component: ComponentType, + config?: DialogConfig>, + ): DialogRef { + this.activeDrawer?.close(); + /** + * This is also circular. When creating the DrawerDialogRef, we do not yet have a portal instance to provide to the injector. + * Similar to `this.open`, we get around this with mutability. + */ + this.activeDrawer = new DrawerDialogRef(this.drawerService); + const portal = new ComponentPortal( + component, + null, + this.createInjector({ data: config?.data, dialogRef: this.activeDrawer }), + ); + this.activeDrawer.portal = portal; + this.drawerService.open(portal); + return this.activeDrawer; } /** @@ -113,8 +209,7 @@ export class DialogService extends Dialog implements OnDestroy { */ async openSimpleDialog(simpleDialogOptions: SimpleDialogOptions): Promise { const dialogRef = this.openSimpleDialogRef(simpleDialogOptions); - - return firstValueFrom(dialogRef.closed); + return firstValueFrom(dialogRef.closed.pipe(map((v: boolean | undefined) => !!v))); } /** @@ -134,20 +229,29 @@ export class DialogService extends Dialog implements OnDestroy { }); } - protected translate(translation: string | Translation, defaultKey?: string): string { - if (translation == null && defaultKey == null) { - return null; - } + /** Close all open dialogs */ + closeAll(): void { + return this.dialog.closeAll(); + } - if (translation == null) { - return this.i18nService.t(defaultKey); - } - - // Translation interface use implies we must localize. - if (typeof translation === "object") { - return this.i18nService.t(translation.key, ...(translation.placeholders ?? [])); - } - - return translation; + /** The injector that is passed to the opened dialog */ + private createInjector(opts: { data: unknown; dialogRef: DialogRef }): Injector { + return Injector.create({ + providers: [ + { + provide: DIALOG_DATA, + useValue: opts.data, + }, + { + provide: DialogRef, + useValue: opts.dialogRef, + }, + { + provide: CdkDialogRefBase, + useValue: opts.dialogRef, + }, + ], + parent: this.injector, + }); } } diff --git a/libs/components/src/dialog/dialog/dialog.component.html b/libs/components/src/dialog/dialog/dialog.component.html index 01f05985127..eaf7fc2beec 100644 --- a/libs/components/src/dialog/dialog/dialog.component.html +++ b/libs/components/src/dialog/dialog/dialog.component.html @@ -1,12 +1,22 @@ +@let isDrawer = dialogRef?.isDrawer;
+ @let showHeaderBorder = !isDrawer || background === "alt" || bodyHasScrolledFrom().top;
-

} -

+

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Ð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 Z%W3>CI$@P~5SgeBd{mD4Gdn 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%0DZzBc#{kuHQ3xWI`Ear!Re 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 E0Ln!2jQ{`u literal 0 HcmV?d00001 diff --git a/libs/components/src/avatar/avatar.component.ts b/libs/components/src/avatar/avatar.component.ts index 0e3dbd6f1b9..554f55636fc 100644 --- a/libs/components/src/avatar/avatar.component.ts +++ b/libs/components/src/avatar/avatar.component.ts @@ -123,7 +123,7 @@ export class AvatarComponent implements OnChanges { textTag.setAttribute("fill", Utils.pickTextColorBasedOnBgColor(color, 135, true)); textTag.setAttribute( "font-family", - '"DM Sans","Helvetica Neue",Helvetica,Arial,' + + 'Roboto,"Helvetica Neue",Helvetica,Arial,' + 'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"', ); // Warning do not use innerHTML here, characters are user provided diff --git a/libs/components/src/variables.scss b/libs/components/src/variables.scss index bc9cded4981..e3651f9c37d 100644 --- a/libs/components/src/variables.scss +++ b/libs/components/src/variables.scss @@ -21,7 +21,7 @@ $body-bg: $white; $body-color: #333333; $font-family-sans-serif: - "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; $h1-font-size: 1.7rem; From 87b875c48b2a9c5d463397a4bdefe42412399a42 Mon Sep 17 00:00:00 2001 From: Patrick-Pimentel-Bitwarden Date: Mon, 12 May 2025 09:29:11 -0400 Subject: [PATCH 014/245] docs(update-auth-approving-clients): [PM-17111] Add Browser to List of Approving Clients - Updated comments. (#14707) --- .../services/platform-utils/browser-platform-utils.service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts index c9200ecc1a4..22708d8e425 100644 --- a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts +++ b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts @@ -26,6 +26,10 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.deviceCache; } + // ORDERING MATTERS HERE + // Ordered from most specific to least specific. We try to discern the greatest detail + // for the type of extension the user is on by checking specific cases first and as we go down + // the list we hope to catch all by the most generic clients they could be on. if (BrowserPlatformUtilsService.isFirefox()) { this.deviceCache = DeviceType.FirefoxExtension; } else if (BrowserPlatformUtilsService.isOpera(globalContext)) { From aca8ab8e40f18108ddbf3a1808c545bc1571b1f7 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 12 May 2025 15:31:03 +0200 Subject: [PATCH 015/245] [PM-21001] Move autofill code to new encrypt service interface (#14548) * Move autofill code to new encrypt service interface * Fix test runner --- .../src/native-message.service.ts | 8 ++------ .../src/services/duckduckgo-message-handler.service.ts | 5 ++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts index c01d581afe8..c2356f93e28 100644 --- a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts @@ -220,7 +220,7 @@ export default class NativeMessageService { const sharedKey = await this.getSharedKeyForKey(key); - return this.encryptService.encrypt(commandDataString, sharedKey); + return this.encryptService.encryptString(commandDataString, sharedKey); } private async decryptResponsePayload( @@ -228,11 +228,7 @@ export default class NativeMessageService { key: string, ): Promise { const sharedKey = await this.getSharedKeyForKey(key); - const decrypted = await this.encryptService.decryptToUtf8( - payload, - sharedKey, - "native-messaging-session", - ); + const decrypted = await this.encryptService.decryptString(payload, sharedKey); return JSON.parse(decrypted); } diff --git a/apps/desktop/src/services/duckduckgo-message-handler.service.ts b/apps/desktop/src/services/duckduckgo-message-handler.service.ts index e82444be993..6fb91231be1 100644 --- a/apps/desktop/src/services/duckduckgo-message-handler.service.ts +++ b/apps/desktop/src/services/duckduckgo-message-handler.service.ts @@ -168,7 +168,7 @@ export class DuckDuckGoMessageHandlerService { payload: DecryptedCommandData, key: SymmetricCryptoKey, ): Promise { - return await this.encryptService.encrypt(JSON.stringify(payload), key); + return await this.encryptService.encryptString(JSON.stringify(payload), key); } private async decryptPayload(message: EncryptedMessage): Promise { @@ -188,10 +188,9 @@ export class DuckDuckGoMessageHandlerService { } try { - let decryptedResult = await this.encryptService.decryptToUtf8( + let decryptedResult = await this.encryptService.decryptString( message.encryptedCommand as EncString, this.duckduckgoSharedSecret, - "ddg-shared-key", ); decryptedResult = this.trimNullCharsFromMessage(decryptedResult); From 57ed1e7285f8a263429140a19ae0e0ff32258dc6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 10:09:40 -0400 Subject: [PATCH 016/245] [deps] Platform: Update @types/node to v22.15.3 (#14723) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../native-messaging-test-runner/package-lock.json | 8 ++++---- apps/desktop/native-messaging-test-runner/package.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index d506e109e94..8b39fd9805e 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -17,7 +17,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.14.1", + "@types/node": "22.15.3", "typescript": "5.4.2" } }, @@ -101,9 +101,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.14.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", - "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "version": "22.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", + "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index f67ab259d3b..ea6b1b3e7a8 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -22,7 +22,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.14.1", + "@types/node": "22.15.3", "typescript": "5.4.2" }, "_moduleAliases": { diff --git a/package-lock.json b/package-lock.json index 1e7e0741cd8..a15275a695e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -110,7 +110,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.14.1", + "@types/node": "22.15.3", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/papaparse": "5.3.15", @@ -11656,9 +11656,9 @@ } }, "node_modules/@types/node": { - "version": "22.14.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", - "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "version": "22.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", + "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 81f1c92d5a5..904ae121cc9 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.14.1", + "@types/node": "22.15.3", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/papaparse": "5.3.15", From 89b9ba21cabbd4ddaf71bba9f267cd646bd4fc9b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 07:15:06 -0700 Subject: [PATCH 017/245] [deps] Platform: Update Rust crate arboard to v3.5.0 (#14484) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 153 ++++++++----------------- apps/desktop/desktop_native/Cargo.toml | 2 +- 2 files changed, 46 insertions(+), 109 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index cfd29303510..1b442fbbb8a 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -120,9 +120,9 @@ checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arboard" -version = "3.4.1" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" +checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70" dependencies = [ "clipboard-win", "log", @@ -130,6 +130,7 @@ dependencies = [ "objc2-app-kit", "objc2-foundation", "parking_lot", + "percent-encoding", "wl-clipboard-rs", "x11rb", ] @@ -465,15 +466,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2", -] - [[package]] name = "blocking" version = "1.6.1" @@ -565,12 +557,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - [[package]] name = "cfg_aliases" version = "0.2.1" @@ -867,17 +853,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "derive-new" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "desktop_core" version = "0.0.0" @@ -1007,6 +982,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags", + "objc2", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1409,7 +1394,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bdbbd5bc8c5749697ccaa352fa45aff8730cf21c68029c0eef1ffed7c3d6ba2" dependencies = [ "cfg-if", - "nix 0.29.0", + "nix", "widestring", "windows 0.57.0", ] @@ -1839,18 +1824,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "nix" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases 0.1.1", - "libc", -] - [[package]] name = "nix" version = "0.29.0" @@ -1859,7 +1832,7 @@ checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags", "cfg-if", - "cfg_aliases 0.2.1", + "cfg_aliases", "libc", "memoffset", ] @@ -1990,47 +1963,24 @@ dependencies = [ "libc", ] -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - [[package]] name = "objc2" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" dependencies = [ - "objc-sys", "objc2-encode", ] [[package]] name = "objc2-app-kit" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ "bitflags", - "block2", - "libc", - "objc2", - "objc2-core-data", - "objc2-core-image", - "objc2-foundation", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-core-data" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" -dependencies = [ - "bitflags", - "block2", "objc2", + "objc2-core-graphics", "objc2-foundation", ] @@ -2041,18 +1991,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ "bitflags", + "dispatch2", + "objc2", ] [[package]] -name = "objc2-core-image" -version = "0.2.2" +name = "objc2-core-graphics" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" dependencies = [ - "block2", + "bitflags", + "dispatch2", "objc2", - "objc2-foundation", - "objc2-metal", + "objc2-core-foundation", + "objc2-io-surface", ] [[package]] @@ -2063,14 +2016,13 @@ checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "objc2-foundation" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ "bitflags", - "block2", - "libc", "objc2", + "objc2-core-foundation", ] [[package]] @@ -2084,28 +2036,14 @@ dependencies = [ ] [[package]] -name = "objc2-metal" -version = "0.2.2" +name = "objc2-io-surface" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" dependencies = [ "bitflags", - "block2", "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags", - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", + "objc2-core-foundation", ] [[package]] @@ -3487,9 +3425,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.31.2" +version = "0.32.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc" dependencies = [ "bitflags", "wayland-backend", @@ -3499,9 +3437,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.2.0" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +checksum = "248a02e6f595aad796561fa82d25601bd2c8c3b145b1c7453fc8f94c1a58f8b2" dependencies = [ "bitflags", "wayland-backend", @@ -3982,15 +3920,14 @@ dependencies = [ [[package]] name = "wl-clipboard-rs" -version = "0.8.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b41773911497b18ca8553c3daaf8ec9fe9819caf93d451d3055f69de028adb" +checksum = "2a083daad7e8a4b8805ad73947ccadabe62afe37ce0e9787a56ff373d34762c7" dependencies = [ - "derive-new", "libc", "log", - "nix 0.28.0", "os_pipe", + "rustix", "tempfile", "thiserror 1.0.69", "tree_magic_mini", @@ -4085,7 +4022,7 @@ dependencies = [ "futures-sink", "futures-util", "hex", - "nix 0.29.0", + "nix", "ordered-stream", "rand 0.8.5", "serde", @@ -4115,7 +4052,7 @@ dependencies = [ "futures-core", "futures-lite", "hex", - "nix 0.29.0", + "nix", "ordered-stream", "serde", "serde_repr", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index f613744f42a..fafaf02eca3 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -11,7 +11,7 @@ publish = false [workspace.dependencies] aes = "=0.8.4" anyhow = "=1.0.94" -arboard = { version = "=3.4.1", default-features = false } +arboard = { version = "=3.5.0", default-features = false } argon2 = "=0.5.3" base64 = "=0.22.1" bindgen = "0.71.1" From 07725853a287c679964a608f3b23695c54030575 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 12 May 2025 17:00:53 +0200 Subject: [PATCH 018/245] Add tests for masterpasswordservice (#14728) * Add tests for masterpasswordservice * Fix tests --- .../services/master-password.service.spec.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/libs/common/src/key-management/master-password/services/master-password.service.spec.ts b/libs/common/src/key-management/master-password/services/master-password.service.spec.ts index 93439ac8caa..b55e770f865 100644 --- a/libs/common/src/key-management/master-password/services/master-password.service.spec.ts +++ b/libs/common/src/key-management/master-password/services/master-password.service.spec.ts @@ -2,12 +2,16 @@ import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; import * as rxjs from "rxjs"; +import { makeSymmetricCryptoKey } from "../../../../spec"; import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason"; import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service"; import { LogService } from "../../../platform/abstractions/log.service"; import { StateService } from "../../../platform/abstractions/state.service"; +import { EncString } from "../../../platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { StateProvider } from "../../../platform/state"; import { UserId } from "../../../types/guid"; +import { MasterKey } from "../../../types/key"; import { EncryptService } from "../../crypto/abstractions/encrypt.service"; import { MasterPasswordService } from "./master-password.service"; @@ -27,6 +31,14 @@ describe("MasterPasswordService", () => { update: jest.fn().mockResolvedValue(null), }; + const testUserKey: SymmetricCryptoKey = makeSymmetricCryptoKey(64, 1); + const testMasterKey: MasterKey = makeSymmetricCryptoKey(32, 2); + const testStretchedMasterKey: SymmetricCryptoKey = makeSymmetricCryptoKey(64, 3); + const testMasterKeyEncryptedKey = + "0.gbauOANURUHqvhLTDnva1A==|nSW+fPumiuTaDB/s12+JO88uemV6rhwRSR+YR1ZzGr5j6Ei3/h+XEli2Unpz652NlZ9NTuRpHxeOqkYYJtp7J+lPMoclgteXuAzUu9kqlRc="; + const testStretchedMasterKeyEncryptedKey = + "2.gbauOANURUHqvhLTDnva1A==|nSW+fPumiuTaDB/s12+JO88uemV6rhwRSR+YR1ZzGr5j6Ei3/h+XEli2Unpz652NlZ9NTuRpHxeOqkYYJtp7J+lPMoclgteXuAzUu9kqlRc=|DeUFkhIwgkGdZA08bDnDqMMNmZk21D+H5g8IostPKAY="; + beforeEach(() => { stateProvider = mock(); stateService = mock(); @@ -45,6 +57,9 @@ describe("MasterPasswordService", () => { encryptService, logService, ); + + encryptService.unwrapSymmetricKey.mockResolvedValue(makeSymmetricCryptoKey(64, 1)); + keyGenerationService.stretchKey.mockResolvedValue(makeSymmetricCryptoKey(64, 3)); }); describe("setForceSetPasswordReason", () => { @@ -101,4 +116,41 @@ describe("MasterPasswordService", () => { expect(mockUserState.update).toHaveBeenCalled(); }); }); + describe("decryptUserKeyWithMasterKey", () => { + it("decrypts a userkey wrapped in AES256-CBC", async () => { + encryptService.unwrapSymmetricKey.mockResolvedValue(testUserKey); + await sut.decryptUserKeyWithMasterKey( + testMasterKey, + userId, + new EncString(testMasterKeyEncryptedKey), + ); + expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith( + new EncString(testMasterKeyEncryptedKey), + testMasterKey, + ); + }); + it("decrypts a userkey wrapped in AES256-CBC-HMAC", async () => { + encryptService.unwrapSymmetricKey.mockResolvedValue(testUserKey); + keyGenerationService.stretchKey.mockResolvedValue(testStretchedMasterKey); + await sut.decryptUserKeyWithMasterKey( + testMasterKey, + userId, + new EncString(testStretchedMasterKeyEncryptedKey), + ); + expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith( + new EncString(testStretchedMasterKeyEncryptedKey), + testStretchedMasterKey, + ); + expect(keyGenerationService.stretchKey).toHaveBeenCalledWith(testMasterKey); + }); + it("returns null if failed to decrypt", async () => { + encryptService.unwrapSymmetricKey.mockResolvedValue(null); + const result = await sut.decryptUserKeyWithMasterKey( + testMasterKey, + userId, + new EncString(testStretchedMasterKeyEncryptedKey), + ); + expect(result).toBeNull(); + }); + }); }); From 2487e9b98deb32b10a43bd93cc839d4556223ad4 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Mon, 12 May 2025 11:13:49 -0400 Subject: [PATCH 019/245] [PM-20637] Trigger password reprompt when updating a reprompt cipher via notification (#14575) * reprompt user on cipher update when enabled Co-authored-by: Daniel Riera * update tests * add test --------- Co-authored-by: Daniel Riera --- .../notification.background.spec.ts | 57 ++++++++++++++++++- .../background/notification.background.ts | 42 ++++++++++++-- .../overlay-notifications-content.service.ts | 1 + .../overlay-notifications-content.service.ts | 6 +- .../services/abstractions/autofill.service.ts | 6 +- .../src/autofill/services/autofill.service.ts | 9 ++- .../vault-v2/view-v2/view-v2.component.ts | 27 +++++++-- libs/common/src/autofill/constants/index.ts | 3 +- 8 files changed, 134 insertions(+), 17 deletions(-) diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 63ae1193737..009efd7ff36 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -17,6 +17,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { UserId } from "@bitwarden/common/types/guid"; +import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; @@ -828,6 +829,7 @@ describe("NotificationBackground", () => { id: "testId", name: "testItemName", login: { username: "testUser" }, + reprompt: CipherRepromptType.None, }); getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView); @@ -842,6 +844,7 @@ describe("NotificationBackground", () => { message.edit, sender.tab, "testId", + false, ); expect(updateWithServerSpy).toHaveBeenCalled(); expect(tabSendMessageDataSpy).toHaveBeenCalledWith( @@ -855,6 +858,55 @@ describe("NotificationBackground", () => { ); }); + it("prompts the user for master password entry if the notification message type is for ChangePassword and the cipher reprompt is enabled", async () => { + const tab = createChromeTabMock({ id: 1, url: "https://example.com" }); + const sender = mock({ tab }); + const message: NotificationBackgroundExtensionMessage = { + command: "bgSaveCipher", + edit: false, + folder: "folder-id", + }; + const queueMessage = mock({ + type: NotificationQueueMessageType.ChangePassword, + tab, + domain: "example.com", + newPassword: "newPassword", + }); + notificationBackground["notificationQueue"] = [queueMessage]; + const cipherView = mock({ + id: "testId", + name: "testItemName", + login: { username: "testUser" }, + reprompt: CipherRepromptType.Password, + }); + getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView); + + sendMockExtensionMessage(message, sender); + await flushPromises(); + + expect(editItemSpy).not.toHaveBeenCalled(); + expect(autofillService.isPasswordRepromptRequired).toHaveBeenCalled(); + expect(createWithServerSpy).not.toHaveBeenCalled(); + expect(updatePasswordSpy).toHaveBeenCalledWith( + cipherView, + queueMessage.newPassword, + message.edit, + sender.tab, + "testId", + false, + ); + expect(updateWithServerSpy).not.toHaveBeenCalled(); + expect(tabSendMessageDataSpy).not.toHaveBeenCalledWith( + sender.tab, + "saveCipherAttemptCompleted", + { + itemName: "testItemName", + cipherId: cipherView.id, + task: undefined, + }, + ); + }); + it("completes password update notification with a security task notice if any are present for the cipher, and dismisses tasks for the updated cipher", async () => { const mockCipherId = "testId"; const mockOrgId = "testOrgId"; @@ -905,6 +957,7 @@ describe("NotificationBackground", () => { id: mockCipherId, organizationId: mockOrgId, name: "Test Item", + reprompt: CipherRepromptType.None, }); getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView); @@ -919,6 +972,7 @@ describe("NotificationBackground", () => { message.edit, sender.tab, mockCipherId, + false, ); expect(updateWithServerSpy).toHaveBeenCalled(); expect(tabSendMessageDataSpy).toHaveBeenCalledWith( @@ -1000,6 +1054,7 @@ describe("NotificationBackground", () => { message.edit, sender.tab, "testId", + false, ); expect(editItemSpy).toHaveBeenCalled(); expect(updateWithServerSpy).not.toHaveBeenCalled(); @@ -1170,7 +1225,7 @@ describe("NotificationBackground", () => { newPassword: "newPassword", }); notificationBackground["notificationQueue"] = [queueMessage]; - const cipherView = mock(); + const cipherView = mock({ reprompt: CipherRepromptType.None }); getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView); const errorMessage = "fetch error"; updateWithServerSpy.mockImplementation(() => { diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 5dd15274677..52920ec67a8 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -14,6 +14,7 @@ import { ExtensionCommand, ExtensionCommandType, NOTIFICATION_BAR_LIFESPAN_MS, + UPDATE_PASSWORD, } from "@bitwarden/common/autofill/constants"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service"; @@ -104,6 +105,8 @@ export default class NotificationBackground { this.removeTabFromNotificationQueue(sender.tab), bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab), bgSaveCipher: ({ message, sender }) => this.handleSaveCipherMessage(message, sender), + bgHandleReprompt: ({ message, sender }: any) => + this.handleCipherUpdateRepromptResponse(message), bgUnlockPopoutOpened: ({ message, sender }) => this.unlockVault(message, sender.tab), checkNotificationQueue: ({ sender }) => this.checkNotificationQueue(sender.tab), collectPageDetailsResponse: ({ message }) => @@ -631,6 +634,17 @@ export default class NotificationBackground { await this.saveOrUpdateCredentials(sender.tab, message.edit, message.folder); } + async handleCipherUpdateRepromptResponse(message: NotificationBackgroundExtensionMessage) { + if (message.success) { + await this.saveOrUpdateCredentials(message.tab, false, undefined, true); + } else { + await BrowserApi.tabSendMessageData(message.tab, "saveCipherAttemptCompleted", { + error: "Password reprompt failed", + }); + return; + } + } + /** * Saves or updates credentials based on the message within the * notification queue that is associated with the specified tab. @@ -639,7 +653,12 @@ export default class NotificationBackground { * @param edit - Identifies if the credentials should be edited or simply added * @param folderId - The folder to add the cipher to */ - private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, edit: boolean, folderId?: string) { + private async saveOrUpdateCredentials( + tab: chrome.tabs.Tab, + edit: boolean, + folderId?: string, + skipReprompt: boolean = false, + ) { for (let i = this.notificationQueue.length - 1; i >= 0; i--) { const queueMessage = this.notificationQueue[i]; if ( @@ -654,18 +673,26 @@ export default class NotificationBackground { continue; } - this.notificationQueue.splice(i, 1); - const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(getOptionalUserId), ); if (queueMessage.type === NotificationQueueMessageType.ChangePassword) { const cipherView = await this.getDecryptedCipherById(queueMessage.cipherId, activeUserId); - await this.updatePassword(cipherView, queueMessage.newPassword, edit, tab, activeUserId); + + await this.updatePassword( + cipherView, + queueMessage.newPassword, + edit, + tab, + activeUserId, + skipReprompt, + ); return; } + this.notificationQueue.splice(i, 1); + // If the vault was locked, check if a cipher needs updating instead of creating a new one if (queueMessage.wasVaultLocked) { const allCiphers = await this.cipherService.getAllDecryptedForUrl( @@ -725,6 +752,7 @@ export default class NotificationBackground { edit: boolean, tab: chrome.tabs.Tab, userId: UserId, + skipReprompt: boolean = false, ) { cipherView.login.password = newPassword; @@ -758,6 +786,12 @@ export default class NotificationBackground { } : undefined; + if (cipherView.reprompt && !skipReprompt) { + await this.autofillService.isPasswordRepromptRequired(cipherView, tab, UPDATE_PASSWORD); + + return; + } + await this.cipherService.updateWithServer(cipher); await BrowserApi.tabSendMessageData(tab, "saveCipherAttemptCompleted", { diff --git a/apps/browser/src/autofill/overlay/notifications/abstractions/overlay-notifications-content.service.ts b/apps/browser/src/autofill/overlay/notifications/abstractions/overlay-notifications-content.service.ts index 82c03cacadf..42d7666e8a7 100644 --- a/apps/browser/src/autofill/overlay/notifications/abstractions/overlay-notifications-content.service.ts +++ b/apps/browser/src/autofill/overlay/notifications/abstractions/overlay-notifications-content.service.ts @@ -15,6 +15,7 @@ export type NotificationsExtensionMessage = { typeData?: NotificationTypeData; height?: number; error?: string; + closedByUser?: boolean; fadeOutNotification?: boolean; }; }; diff --git a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts index 519521feaa9..c21aaa37dd4 100644 --- a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts +++ b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts @@ -106,13 +106,15 @@ export class OverlayNotificationsContentService * @param message - The message containing the data for closing the notification bar. */ private handleCloseNotificationBarMessage(message: NotificationsExtensionMessage) { + const closedByUser = + typeof message.data?.closedByUser === "boolean" ? message.data.closedByUser : true; if (message.data?.fadeOutNotification) { setElementStyles(this.notificationBarIframeElement, { opacity: "0" }, true); - globalThis.setTimeout(() => this.closeNotificationBar(true), 150); + globalThis.setTimeout(() => this.closeNotificationBar(closedByUser), 150); return; } - this.closeNotificationBar(true); + this.closeNotificationBar(closedByUser); } /** diff --git a/apps/browser/src/autofill/services/abstractions/autofill.service.ts b/apps/browser/src/autofill/services/abstractions/autofill.service.ts index 5b1b4b3b8bb..daafd871789 100644 --- a/apps/browser/src/autofill/services/abstractions/autofill.service.ts +++ b/apps/browser/src/autofill/services/abstractions/autofill.service.ts @@ -87,5 +87,9 @@ export abstract class AutofillService { cipherType?: CipherType, ) => Promise; setAutoFillOnPageLoadOrgPolicy: () => Promise; - isPasswordRepromptRequired: (cipher: CipherView, tab: chrome.tabs.Tab) => Promise; + isPasswordRepromptRequired: ( + cipher: CipherView, + tab: chrome.tabs.Tab, + action?: string, + ) => Promise; } diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index da46ceb0864..525010bacc1 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -593,15 +593,20 @@ export default class AutofillService implements AutofillServiceInterface { * * @param cipher - The cipher to autofill * @param tab - The tab to autofill + * @param action - override for default action once reprompt is completed successfully */ - async isPasswordRepromptRequired(cipher: CipherView, tab: chrome.tabs.Tab): Promise { + async isPasswordRepromptRequired( + cipher: CipherView, + tab: chrome.tabs.Tab, + action?: string, + ): Promise { const userHasMasterPasswordAndKeyHash = await this.userVerificationService.hasMasterPasswordAndMasterKeyHash(); if (cipher.reprompt === CipherRepromptType.Password && userHasMasterPasswordAndKeyHash) { if (!this.isDebouncingPasswordRepromptPopout()) { await this.openVaultItemPasswordRepromptPopout(tab, { cipherId: cipher.id, - action: "autofill", + action: action ?? "autofill", }); } diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index 56db47619b0..a834314560b 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -19,6 +19,7 @@ import { COPY_USERNAME_ID, COPY_VERIFICATION_CODE_ID, SHOW_AUTOFILL_BUTTON, + UPDATE_PASSWORD, } from "@bitwarden/common/autofill/constants"; import { EventType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -49,6 +50,7 @@ import { PasswordRepromptService, } from "@bitwarden/vault"; +import { sendExtensionMessage } from "../../../../../autofill/utils/index"; import { BrowserApi } from "../../../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component"; @@ -72,7 +74,8 @@ type LoadAction = | typeof SHOW_AUTOFILL_BUTTON | typeof COPY_USERNAME_ID | typeof COPY_PASSWORD_ID - | typeof COPY_VERIFICATION_CODE_ID; + | typeof COPY_VERIFICATION_CODE_ID + | typeof UPDATE_PASSWORD; @Component({ selector: "app-view-v2", @@ -294,7 +297,7 @@ export class ViewV2Component { // Both vaultPopupAutofillService and copyCipherFieldService will perform password re-prompting internally. switch (loadAction) { - case "show-autofill-button": + case SHOW_AUTOFILL_BUTTON: // This action simply shows the cipher view, no need to do anything. if ( this.cipher.reprompt !== CipherRepromptType.None && @@ -303,30 +306,42 @@ export class ViewV2Component { await closeViewVaultItemPopout(`${VaultPopoutType.viewVaultItem}_${this.cipher.id}`); } return; - case "autofill": + case AUTOFILL_ID: actionSuccess = await this.vaultPopupAutofillService.doAutofill(this.cipher, false); break; - case "copy-username": + case COPY_USERNAME_ID: actionSuccess = await this.copyCipherFieldService.copy( this.cipher.login.username, "username", this.cipher, ); break; - case "copy-password": + case COPY_PASSWORD_ID: actionSuccess = await this.copyCipherFieldService.copy( this.cipher.login.password, "password", this.cipher, ); break; - case "copy-totp": + case COPY_VERIFICATION_CODE_ID: actionSuccess = await this.copyCipherFieldService.copy( this.cipher.login.totp, "totp", this.cipher, ); break; + case UPDATE_PASSWORD: { + const repromptSuccess = await this.passwordRepromptService.showPasswordPrompt(); + + await sendExtensionMessage("bgHandleReprompt", { + tab: await chrome.tabs.get(senderTabId), + success: repromptSuccess, + }); + + await closeViewVaultItemPopout(`${VaultPopoutType.viewVaultItem}_${this.cipher.id}`); + + break; + } } if (BrowserPopupUtils.inPopout(window)) { diff --git a/libs/common/src/autofill/constants/index.ts b/libs/common/src/autofill/constants/index.ts index 62ad10e9a90..dc79e27b6aa 100644 --- a/libs/common/src/autofill/constants/index.ts +++ b/libs/common/src/autofill/constants/index.ts @@ -38,7 +38,7 @@ export const ClearClipboardDelay = { FiveMinutes: 300, } as const; -/* Context Menu item Ids */ +/* Ids for context menu items and messaging events */ export const AUTOFILL_CARD_ID = "autofill-card"; export const AUTOFILL_ID = "autofill"; export const SHOW_AUTOFILL_BUTTON = "show-autofill-button"; @@ -54,6 +54,7 @@ export const GENERATE_PASSWORD_ID = "generate-password"; export const NOOP_COMMAND_SUFFIX = "noop"; export const ROOT_ID = "root"; export const SEPARATOR_ID = "separator"; +export const UPDATE_PASSWORD = "update-password"; export const NOTIFICATION_BAR_LIFESPAN_MS = 150000; // 150 seconds From 355bddc6c9d5c110e55fe74c5fcfa86ddd85572c Mon Sep 17 00:00:00 2001 From: Bryan Cunningham Date: Mon, 12 May 2025 11:50:29 -0400 Subject: [PATCH 020/245] [CL-301] Enable Storybook test runner. Run a11y tests also (#14646) * Enable Storybook test runner. Run a11y tests also * no need to return checkA11y function * add back decorator removed in error * add test runner to our ownership * add axe-playwright to our ownership --- .github/renovate.json5 | 2 + .storybook/preview.tsx | 7 + .storybook/test-runner.ts | 50 + package-lock.json | 2149 ++++++++++++++++++++++++++++++++++--- package.json | 4 + tsconfig.eslint.json | 1 + 6 files changed, 2062 insertions(+), 151 deletions(-) create mode 100644 .storybook/test-runner.ts diff --git a/.github/renovate.json5 b/.github/renovate.json5 index c1045739506..e5cd47077fb 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -312,6 +312,7 @@ "@angular/platform-browser", "@angular/platform", "@angular/router", + "axe-playwright", "@compodoc/compodoc", "@ng-select/ng-select", "@storybook/addon-a11y", @@ -320,6 +321,7 @@ "@storybook/addon-essentials", "@storybook/addon-interactions", "@storybook/addon-links", + "@storybook/test-runner", "@storybook/addon-themes", "@storybook/angular", "@storybook/manager-api", diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 6bd28cfe809..19a4fc51dc8 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -26,6 +26,13 @@ const preview: Preview = { wrapperDecorator, ], parameters: { + a11y: { + element: "#storybook-root", + manual: true, + options: { + runOnly: ["section508", "wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"], + }, + }, controls: { matchers: { color: /(background|color)$/i, diff --git a/.storybook/test-runner.ts b/.storybook/test-runner.ts new file mode 100644 index 00000000000..8584a321090 --- /dev/null +++ b/.storybook/test-runner.ts @@ -0,0 +1,50 @@ +import { + // waitForPageReady, + type TestRunnerConfig, +} from "@storybook/test-runner"; +import { injectAxe, checkA11y } from "axe-playwright"; + +const testRunnerConfig: TestRunnerConfig = { + setup() {}, + + async preVisit(page, context) { + return await injectAxe(page); + }, + + async postVisit(page, context) { + await page.waitForSelector("#storybook-root"); + // https://github.com/abhinaba-ghosh/axe-playwright#parameters-on-checka11y-axerun + await checkA11y( + // Playwright page instance. + page, + + // context + "#storybook-root", + + // axeOptions, see https://www.deque.com/axe/core-documentation/api-documentation/#parameters-axerun + { + detailedReport: true, + detailedReportOptions: { + // Includes the full html for invalid nodes + html: true, + }, + verbose: false, + }, + + // skipFailures + false, + + // reporter "v2" is terminal reporter, "html" writes results to file + "v2", + + // axeHtmlReporterOptions + // NOTE: set reporter param (above) to "html" to activate these options + { + outputDir: "reports/a11y", + reportFileName: `${context.id}.html`, + }, + ); + }, +}; + +export default testRunnerConfig; diff --git a/package-lock.json b/package-lock.json index a15275a695e..81c9384fbbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,6 +91,7 @@ "@storybook/addon-essentials": "8.6.12", "@storybook/addon-interactions": "8.6.12", "@storybook/addon-links": "8.6.12", + "@storybook/test-runner": "0.22.0", "@storybook/addon-themes": "8.6.12", "@storybook/angular": "8.6.12", "@storybook/manager-api": "8.6.12", @@ -123,6 +124,7 @@ "@yao-pkg/pkg": "5.16.1", "angular-eslint": "18.4.3", "autoprefixer": "10.4.21", + "axe-playwright": "2.1.0", "babel-loader": "9.2.1", "base64-loader": "1.0.0", "browserslist": "4.23.2", @@ -3327,7 +3329,6 @@ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -3453,7 +3454,6 @@ "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -3580,7 +3580,6 @@ "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -4714,8 +4713,7 @@ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@bitwarden/admin-console": { "resolved": "libs/admin-console", @@ -7179,7 +7177,6 @@ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -7197,7 +7194,6 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -7208,7 +7204,6 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -7223,7 +7218,6 @@ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -7238,7 +7232,6 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -7252,7 +7245,6 @@ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -7269,7 +7261,6 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -7283,7 +7274,6 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7293,8 +7283,7 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", @@ -7312,7 +7301,6 @@ "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -7331,7 +7319,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7342,7 +7329,6 @@ "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", @@ -7391,7 +7377,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -7405,7 +7390,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -7420,8 +7404,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@jest/core/node_modules/slash": { "version": "3.0.0", @@ -7429,11 +7412,23 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } }, + "node_modules/@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -7456,7 +7451,6 @@ "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" @@ -7502,7 +7496,6 @@ "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -7519,7 +7512,6 @@ "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", @@ -7564,7 +7556,6 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7577,7 +7568,6 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7599,7 +7589,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -7613,7 +7602,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7637,7 +7625,6 @@ "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", @@ -7653,7 +7640,6 @@ "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", @@ -7670,7 +7656,6 @@ "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", @@ -7687,7 +7672,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7698,7 +7682,6 @@ "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -7725,8 +7708,7 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@jest/transform/node_modules/slash": { "version": "3.0.0", @@ -7734,7 +7716,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -10819,6 +10800,43 @@ "storybook": "^8.6.12" } }, + "node_modules/@storybook/test-runner": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@storybook/test-runner/-/test-runner-0.22.0.tgz", + "integrity": "sha512-fKY6MTE/bcvMaulKXy+z0fPmRXJx1REkYMOMcGn8zn6uffyBigGgaVf/sZ+AZfibwvjzg/StWhJ9HvAM8pc14g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5", + "@jest/types": "^29.6.3", + "@storybook/csf": "^0.1.11", + "@swc/core": "^1.5.22", + "@swc/jest": "^0.2.23", + "expect-playwright": "^0.8.0", + "jest": "^29.6.4", + "jest-circus": "^29.6.4", + "jest-environment-node": "^29.6.4", + "jest-junit": "^16.0.0", + "jest-playwright-preset": "^4.0.0", + "jest-runner": "^29.6.4", + "jest-serializer-html": "^7.1.0", + "jest-watch-typeahead": "^2.0.0", + "nyc": "^15.1.0", + "playwright": "^1.14.0" + }, + "bin": { + "test-storybook": "dist/test-storybook.js" + }, + "engines": { + "node": "^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "storybook": "^0.0.0-0 || ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 || ^9.0.0-0" + } + }, "node_modules/@storybook/theming": { "version": "8.6.12", "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.6.12.tgz", @@ -10882,6 +10900,250 @@ "storybook": "^8.6.12" } }, + "node_modules/@swc/core": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.24.tgz", + "integrity": "sha512-MaQEIpfcEMzx3VWWopbofKJvaraqmL6HbLlw2bFZ7qYqYw3rkhM0cQVEgyzbHtTWwCwPMFZSC2DUbhlZgrMfLg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.21" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.11.24", + "@swc/core-darwin-x64": "1.11.24", + "@swc/core-linux-arm-gnueabihf": "1.11.24", + "@swc/core-linux-arm64-gnu": "1.11.24", + "@swc/core-linux-arm64-musl": "1.11.24", + "@swc/core-linux-x64-gnu": "1.11.24", + "@swc/core-linux-x64-musl": "1.11.24", + "@swc/core-win32-arm64-msvc": "1.11.24", + "@swc/core-win32-ia32-msvc": "1.11.24", + "@swc/core-win32-x64-msvc": "1.11.24" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.24.tgz", + "integrity": "sha512-dhtVj0PC1APOF4fl5qT2neGjRLgHAAYfiVP8poJelhzhB/318bO+QCFWAiimcDoyMgpCXOhTp757gnoJJrheWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.24.tgz", + "integrity": "sha512-H/3cPs8uxcj2Fe3SoLlofN5JG6Ny5bl8DuZ6Yc2wr7gQFBmyBkbZEz+sPVgsID7IXuz7vTP95kMm1VL74SO5AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.24.tgz", + "integrity": "sha512-PHJgWEpCsLo/NGj+A2lXZ2mgGjsr96ULNW3+T3Bj2KTc8XtMUkE8tmY2Da20ItZOvPNC/69KroU7edyo1Flfbw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.24.tgz", + "integrity": "sha512-C2FJb08+n5SD4CYWCTZx1uR88BN41ZieoHvI8A55hfVf2woT8+6ZiBzt74qW2g+ntZ535Jts5VwXAKdu41HpBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.24.tgz", + "integrity": "sha512-ypXLIdszRo0re7PNNaXN0+2lD454G8l9LPK/rbfRXnhLWDBPURxzKlLlU/YGd2zP98wPcVooMmegRSNOKfvErw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.24.tgz", + "integrity": "sha512-IM7d+STVZD48zxcgo69L0yYptfhaaE9cMZ+9OoMxirNafhKKXwoZuufol1+alEFKc+Wbwp+aUPe/DeWC/Lh3dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.24.tgz", + "integrity": "sha512-DZByJaMVzSfjQKKQn3cqSeqwy6lpMaQDQQ4HPlch9FWtDx/dLcpdIhxssqZXcR2rhaQVIaRQsCqwV6orSDGAGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.24.tgz", + "integrity": "sha512-Q64Ytn23y9aVDKN5iryFi8mRgyHw3/kyjTjT4qFCa8AEb5sGUuSj//AUZ6c0J7hQKMHlg9do5Etvoe61V98/JQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.24.tgz", + "integrity": "sha512-9pKLIisE/Hh2vJhGIPvSoTK4uBSPxNVyXHmOrtdDot4E1FUUI74Vi8tFdlwNbaj8/vusVnb8xPXsxF1uB0VgiQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.24.tgz", + "integrity": "sha512-sybnXtOsdB+XvzVFlBVGgRHLqp3yRpHK7CrmpuDKszhj/QhmsaZzY/GHSeALlMtLup13M0gqbcQvsTNlAHTg3w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/jest": { + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.38.tgz", + "integrity": "sha512-HMoZgXWMqChJwffdDjvplH53g9G2ALQes3HKXDEdliB/b85OQ0CTSbxG8VSeCwiAn7cOaDVEt4mwmZvbHcS52w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/create-cache-key-function": "^29.7.0", + "@swc/counter": "^0.1.3", + "jsonc-parser": "^3.2.0" + }, + "engines": { + "npm": ">= 7.0.0" + }, + "peerDependencies": { + "@swc/core": "*" + } + }, + "node_modules/@swc/types": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.21.tgz", + "integrity": "sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -11102,7 +11364,6 @@ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -11117,7 +11378,6 @@ "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.0.0" } @@ -11128,7 +11388,6 @@ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -11140,7 +11399,6 @@ "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.20.7" } @@ -11336,7 +11594,6 @@ "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -11506,6 +11763,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/junit-report-builder": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/junit-report-builder/-/junit-report-builder-3.0.2.tgz", + "integrity": "sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/keygrip": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", @@ -11908,6 +12172,16 @@ "license": "MIT", "optional": true }, + "node_modules/@types/wait-on": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@types/wait-on/-/wait-on-5.3.4.tgz", + "integrity": "sha512-EBsPjFMrFlMbbUFf9D1Fp+PAB2TwmUn7a3YtHyD9RLuTIk1jDd8SxXVAoez2Ciy+8Jsceo2MYEYZzJ/DvorOKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/webpack-env": { "version": "1.18.8", "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.8.tgz", @@ -13845,6 +14119,19 @@ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/archiver": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", @@ -13981,6 +14268,13 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true, + "license": "MIT" + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -14356,6 +14650,39 @@ "node": ">=4" } }, + "node_modules/axe-html-reporter": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/axe-html-reporter/-/axe-html-reporter-2.2.11.tgz", + "integrity": "sha512-WlF+xlNVgNVWiM6IdVrsh+N0Cw7qupe5HT9N6Uyi+aN7f6SSi92RDomiP1noW8OWIV85V6x404m5oKMeqRV3tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mustache": "^4.0.1" + }, + "engines": { + "node": ">=8.9.0" + }, + "peerDependencies": { + "axe-core": ">=3" + } + }, + "node_modules/axe-playwright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/axe-playwright/-/axe-playwright-2.1.0.tgz", + "integrity": "sha512-tY48SX56XaAp16oHPyD4DXpybz8Jxdz9P7exTjF/4AV70EGUavk+1fUPWirM0OYBR+YyDx6hUeDvuHVA6fB9YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/junit-report-builder": "^3.0.2", + "axe-core": "^4.10.1", + "axe-html-reporter": "2.2.11", + "junit-report-builder": "^5.1.1", + "picocolors": "^1.1.1" + }, + "peerDependencies": { + "playwright": ">1.0.0" + } + }, "node_modules/axios": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", @@ -14383,7 +14710,6 @@ "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", @@ -14406,7 +14732,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -14435,7 +14760,6 @@ "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -14453,7 +14777,6 @@ "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -14471,7 +14794,6 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -14482,7 +14804,6 @@ "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -14600,7 +14921,6 @@ "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -14628,7 +14948,6 @@ "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" @@ -15090,7 +15409,6 @@ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "node-int64": "^0.4.0" } @@ -15471,6 +15789,68 @@ "node": ">=8" } }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/caching-transform/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caching-transform/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/caching-transform/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/caching-transform/node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -15553,7 +15933,6 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -15670,7 +16049,6 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -16037,8 +16415,7 @@ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/color-convert": { "version": "2.0.1", @@ -16123,6 +16500,13 @@ "node": ">=4.0.0" } }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, "node_modules/compare-version": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", @@ -16751,7 +17135,6 @@ "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -16942,6 +17325,20 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/cwd": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz", + "integrity": "sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-pkg": "^0.1.2", + "fs-exists-sync": "^0.1.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/data-urls": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", @@ -17120,7 +17517,6 @@ "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -17216,6 +17612,22 @@ "node": ">= 10" } }, + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -17342,7 +17754,6 @@ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -17385,6 +17796,100 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/diffable-html": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/diffable-html/-/diffable-html-4.1.0.tgz", + "integrity": "sha512-++kyNek+YBLH8cLXS+iTj/Hiy2s5qkRJEJ8kgu/WHbFrVY2vz9xPFUT+fii2zGF0m1CaojDlQJjkfrCt7YWM1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "htmlparser2": "^3.9.2" + } + }, + "node_modules/diffable-html/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/diffable-html/node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/diffable-html/node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/diffable-html/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/diffable-html/node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/diffable-html/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/diffable-html/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/diffable-html/node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, "node_modules/dir-compare": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-3.3.0.tgz", @@ -18057,7 +18562,6 @@ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -18382,8 +18886,7 @@ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/es6-shim": { "version": "0.35.8", @@ -19185,7 +19688,6 @@ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -19200,6 +19702,19 @@ "node": ">=6" } }, + "node_modules/expand-tilde": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", + "integrity": "sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "os-homedir": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", @@ -19217,6 +19732,24 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/expect-playwright": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/expect-playwright/-/expect-playwright-0.8.0.tgz", + "integrity": "sha512-+kn8561vHAY+dt+0gMqqj1oY+g5xWrsuGMk4QGxotT2WS545nVqqjs37z6hrYfIuucwqthzwJfCJUEYqixyljg==", + "dev": true, + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/exponential-backoff": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", @@ -19596,7 +20129,6 @@ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "bser": "2.1.1" } @@ -19771,6 +20303,58 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-file-up": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz", + "integrity": "sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fs-exists-sync": "^0.1.0", + "resolve-dir": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-pkg": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz", + "integrity": "sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-file-up": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-process": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.10.tgz", + "integrity": "sha512-ncYFnWEIwL7PzmrK1yZtaccN8GhethD37RzBHG6iOZoFYB4vSmLLXfeWJjeN5nMvCJMjOtBvBBF8OgxEcikiZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "~4.1.2", + "commander": "^12.1.0", + "loglevel": "^1.9.2" + }, + "bin": { + "find-process": "bin/find-process.js" + } + }, + "node_modules/find-process/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -20305,6 +20889,27 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/front-matter": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", @@ -20349,6 +20954,16 @@ "dev": true, "license": "MIT" }, + "node_modules/fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fs-extra": { "version": "11.3.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", @@ -20527,7 +21142,6 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -20678,6 +21292,56 @@ "node": ">=10.0" } }, + "node_modules/global-modules": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", + "integrity": "sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", + "integrity": "sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -20942,6 +21606,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -20964,6 +21655,19 @@ "he": "bin/he" } }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -21084,8 +21788,7 @@ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/html-loader": { "version": "5.1.0", @@ -22170,7 +22873,6 @@ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -22475,6 +23177,13 @@ "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==", + "dev": true, + "license": "MIT" + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -22540,6 +23249,16 @@ "dev": true, "license": "MIT" }, + "node_modules/is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -22597,6 +23316,19 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/istanbul-lib-instrument": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", @@ -22614,13 +23346,116 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "license": "ISC", + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/istanbul-lib-processinfo/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==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -22636,7 +23471,6 @@ "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -22652,7 +23486,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -22663,7 +23496,6 @@ "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -22737,7 +23569,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -22765,7 +23596,6 @@ "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", @@ -22781,7 +23611,6 @@ "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -22814,7 +23643,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -22828,7 +23656,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -22843,8 +23670,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-circus/node_modules/slash": { "version": "3.0.0", @@ -22852,7 +23678,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -22863,7 +23688,6 @@ "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", @@ -22898,7 +23722,6 @@ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -22945,7 +23768,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -22959,7 +23781,6 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -22972,7 +23793,6 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -22994,7 +23814,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -23008,7 +23827,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -23023,8 +23841,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-config/node_modules/slash": { "version": "3.0.0", @@ -23032,7 +23849,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -23094,7 +23910,6 @@ "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "detect-newline": "^3.0.0" }, @@ -23108,7 +23923,6 @@ "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -23126,7 +23940,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -23140,7 +23953,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -23155,8 +23967,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-environment-jsdom": { "version": "29.7.0", @@ -23385,7 +24196,6 @@ "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -23414,7 +24224,6 @@ "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -23467,7 +24276,6 @@ "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" @@ -23482,7 +24290,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -23496,7 +24303,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -23511,8 +24317,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-matcher-utils": { "version": "29.7.0", @@ -23660,13 +24465,106 @@ "typescript": "^3.0.0 || ^4.0.0 || ^5.0.0" } }, + "node_modules/jest-playwright-preset": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jest-playwright-preset/-/jest-playwright-preset-4.0.0.tgz", + "integrity": "sha512-+dGZ1X2KqtwXaabVjTGxy0a3VzYfvYsWaRcuO8vMhyclHSOpGSI1+5cmlqzzCwQ3+fv0EjkTc7I5aV9lo08dYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect-playwright": "^0.8.0", + "jest-process-manager": "^0.4.0", + "nyc": "^15.1.0", + "playwright-core": ">=1.2.0", + "rimraf": "^3.0.2", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "jest": "^29.3.1", + "jest-circus": "^29.3.1", + "jest-environment-node": "^29.3.1", + "jest-runner": "^29.3.1" + } + }, + "node_modules/jest-playwright-preset/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jest-playwright-preset/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-playwright-preset/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jest-playwright-preset/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-playwright-preset/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==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" }, @@ -23743,13 +24641,58 @@ "dev": true, "license": "MIT" }, + "node_modules/jest-process-manager": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/jest-process-manager/-/jest-process-manager-0.4.0.tgz", + "integrity": "sha512-80Y6snDyb0p8GG83pDxGI/kQzwVTkCxc7ep5FPe/F6JYdvRDhwr6RzRmPSP7SEwuLhxo80lBS/NqOdUIbHIfhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/wait-on": "^5.2.0", + "chalk": "^4.1.0", + "cwd": "^0.10.0", + "exit": "^0.1.2", + "find-process": "^1.4.4", + "prompts": "^2.4.1", + "signal-exit": "^3.0.3", + "spawnd": "^5.0.0", + "tree-kill": "^1.2.2", + "wait-on": "^7.0.0" + } + }, + "node_modules/jest-process-manager/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/jest-process-manager/node_modules/wait-on": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", + "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "axios": "^1.6.1", + "joi": "^17.11.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "rxjs": "^7.8.1" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/jest-regex-util": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -23760,7 +24703,6 @@ "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -23782,7 +24724,6 @@ "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" @@ -23797,7 +24738,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -23808,7 +24748,6 @@ "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", @@ -23842,7 +24781,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -23853,7 +24791,6 @@ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -23865,7 +24802,6 @@ "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -23900,7 +24836,6 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -23913,7 +24848,6 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -23935,7 +24869,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -23949,18 +24882,26 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } }, + "node_modules/jest-serializer-html": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/jest-serializer-html/-/jest-serializer-html-7.1.0.tgz", + "integrity": "sha512-xYL2qC7kmoYHJo8MYqJkzrl/Fdlx+fat4U1AqYg+kafqwcKPiMkOcjWHPKhueuNEgr+uemhGc+jqXYiwCyRyLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "diffable-html": "^4.1.0" + } + }, "node_modules/jest-snapshot": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", @@ -23993,7 +24934,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -24007,7 +24947,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -24022,8 +24961,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-util": { "version": "29.7.0", @@ -24062,7 +25000,6 @@ "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", @@ -24081,7 +25018,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -24095,7 +25031,6 @@ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -24109,7 +25044,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -24124,8 +25058,111 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, + "license": "MIT" + }, + "node_modules/jest-watch-typeahead": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-2.2.2.tgz", + "integrity": "sha512-+QgOFW4o5Xlgd6jGS5X37i08tuuXNW8X0CV9WNFi+3n8ExCIP+E1melYhvYLjv5fE6D0yyzk74vsSO8I6GqtvQ==", + "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "ansi-escapes": "^6.0.0", + "chalk": "^5.2.0", + "jest-regex-util": "^29.0.0", + "jest-watcher": "^29.0.0", + "slash": "^5.0.0", + "string-length": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "jest": "^27.0.0 || ^28.0.0 || ^29.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/ansi-escapes": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/char-regex": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.2.tgz", + "integrity": "sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", + "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^2.0.0", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } }, "node_modules/jest-watcher": { "version": "29.7.0", @@ -24133,7 +25170,6 @@ "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", @@ -24154,7 +25190,6 @@ "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -24171,7 +25206,6 @@ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -24518,6 +25552,47 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/junit-report-builder": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/junit-report-builder/-/junit-report-builder-5.1.1.tgz", + "integrity": "sha512-ZNOIIGMzqCGcHQEA2Q4rIQQ3Df6gSIfne+X9Rly9Bc2y55KxAZu8iGv+n2pP0bLf0XAOctJZgeloC54hWzCahQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "make-dir": "^3.1.0", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/junit-report-builder/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/junit-report-builder/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/jwt-decode": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", @@ -24897,7 +25972,6 @@ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -25602,6 +26676,13 @@ "license": "MIT", "peer": true }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -26001,7 +27082,6 @@ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "semver": "^7.5.3" }, @@ -26097,7 +27177,6 @@ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "tmpl": "1.0.5" } @@ -27587,6 +28666,16 @@ "readable-stream": "^3.6.0" } }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/mute-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", @@ -28164,8 +29253,7 @@ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/node-machine-id": { "version": "1.1.12", @@ -28173,6 +29261,19 @@ "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", "dev": true }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -28844,6 +29945,350 @@ "node": ">=6" } }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nyc/node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/nyc/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nyc/node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nyc/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/nyc/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -29113,6 +30558,16 @@ "dev": true, "license": "MIT" }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/os-name": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/os-name/-/os-name-4.0.1.tgz", @@ -29270,6 +30725,22 @@ "node": ">=6" } }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -29551,6 +31022,16 @@ "node": ">= 0.10" } }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/parse5": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", @@ -30112,6 +31593,53 @@ "node": ">=4" } }, + "node_modules/playwright": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", + "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", + "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", @@ -30634,6 +32162,19 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/process-on-spawn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", + "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -30808,8 +32349,7 @@ "url": "https://opencollective.com/fast-check" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/qrcode-parser": { "version": "2.1.3", @@ -31308,6 +32848,19 @@ "node": ">= 0.10" } }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "license": "ISC", + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/remark-gfm": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", @@ -31487,6 +33040,13 @@ "node": ">=0.10.0" } }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true, + "license": "ISC" + }, "node_modules/requireindex": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", @@ -31550,6 +33110,20 @@ "node": ">=8" } }, + "node_modules/resolve-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", + "integrity": "sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -32444,6 +34018,13 @@ "node": ">= 0.8" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, + "license": "ISC" + }, "node_modules/set-cookie-parser": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", @@ -32888,6 +34469,164 @@ "node": ">=0.10.0" } }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spawn-wrap/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/spawn-wrap/node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/spawn-wrap/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/spawn-wrap/node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-wrap/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/spawn-wrap/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/spawn-wrap/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/spawn-wrap/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/spawn-wrap/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/spawnd": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-5.0.0.tgz", + "integrity": "sha512-28+AJr82moMVWolQvlAIv3JcYDkjkFTEmfDc503wxrF5l2rQ3dFz6DpbXp3kD4zmgGGldfM4xM4v1sFj/ZaIOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "exit": "^0.1.2", + "signal-exit": "^3.0.3", + "tree-kill": "^1.2.2", + "wait-port": "^0.2.9" + } + }, + "node_modules/spawnd/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -33234,7 +34973,6 @@ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -33364,7 +35102,6 @@ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -33927,7 +35664,6 @@ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -33943,7 +35679,6 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -33956,7 +35691,6 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -33978,7 +35712,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -34148,8 +35881,7 @@ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -34899,6 +36631,16 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, "node_modules/typescript": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", @@ -35490,7 +37232,6 @@ "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -35505,8 +37246,7 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/validate-npm-package-license": { "version": "3.0.4", @@ -36131,6 +37871,109 @@ "tslib": "^2.1.0" } }, + "node_modules/wait-port": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.14.tgz", + "integrity": "sha512-kIzjWcr6ykl7WFbZd0TMae8xovwqcqbx6FM9l+7agOgUByhzdjfzZBPK2CPufldTOMxbUivss//Sh9MFawmPRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.4.2", + "commander": "^3.0.2", + "debug": "^4.1.1" + }, + "bin": { + "wait-port": "bin/wait-port.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wait-port/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wait-port/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wait-port/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/wait-port/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/wait-port/node_modules/commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true, + "license": "MIT" + }, + "node_modules/wait-port/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/wait-port/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/wait-port/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/walk-up-path": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", @@ -36144,7 +37987,6 @@ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "makeerror": "1.0.12" } @@ -36883,6 +38725,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true, + "license": "ISC" + }, "node_modules/which-typed-array": { "version": "1.1.18", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", @@ -37030,7 +38879,6 @@ "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -37044,8 +38892,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/ws": { "version": "8.18.1", diff --git a/package.json b/package.json index 904ae121cc9..8ba1c95df6a 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,8 @@ "storybook": "ng run components:storybook", "build-storybook": "ng run components:build-storybook", "build-storybook:ci": "ng run components:build-storybook --webpack-stats-json", + "test-stories": "test-storybook --url http://localhost:6006", + "test-stories:watch": "test-stories --watch", "postinstall": "patch-package" }, "workspaces": [ @@ -53,6 +55,7 @@ "@storybook/addon-essentials": "8.6.12", "@storybook/addon-interactions": "8.6.12", "@storybook/addon-links": "8.6.12", + "@storybook/test-runner": "0.22.0", "@storybook/addon-themes": "8.6.12", "@storybook/angular": "8.6.12", "@storybook/manager-api": "8.6.12", @@ -85,6 +88,7 @@ "@yao-pkg/pkg": "5.16.1", "angular-eslint": "18.4.3", "autoprefixer": "10.4.21", + "axe-playwright": "2.1.0", "babel-loader": "9.2.1", "base64-loader": "1.0.0", "browserslist": "4.23.2", diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 7ffa34df58c..e8c3f669c0c 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -45,6 +45,7 @@ "files": [ ".storybook/main.ts", ".storybook/manager.js", + ".storybook/test-runner.ts", "apps/browser/src/autofill/content/components/.lit-storybook/main.ts" ], "include": ["apps/**/*", "libs/**/*", "bitwarden_license/**/*", "scripts/**/*"], From a10b9530af5628552583f8afb31cb0d9d6928fd2 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Mon, 12 May 2025 18:20:56 +0100 Subject: [PATCH 021/245] changes to sponsorship is showing in individual vault (#14627) --- .../app/billing/settings/sponsored-families.component.ts | 8 +++++--- .../admin-console/models/data/organization.data.spec.ts | 1 + .../src/admin-console/models/data/organization.data.ts | 2 ++ .../src/admin-console/models/domain/organization.ts | 2 ++ .../models/response/profile-organization.response.ts | 2 ++ 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/web/src/app/billing/settings/sponsored-families.component.ts b/apps/web/src/app/billing/settings/sponsored-families.component.ts index cee08bae8cd..7e493168ce2 100644 --- a/apps/web/src/app/billing/settings/sponsored-families.component.ts +++ b/apps/web/src/app/billing/settings/sponsored-families.component.ts @@ -112,13 +112,15 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy { }); } }); - this.anyOrgsAvailable$ = this.availableSponsorshipOrgs$.pipe(map((orgs) => orgs.length > 0)); this.activeSponsorshipOrgs$ = this.organizationService .organizations$(userId) - .pipe(map((orgs) => orgs.filter((o) => o.familySponsorshipFriendlyName !== null))); - + .pipe( + map((orgs) => + orgs.filter((o) => o.familySponsorshipFriendlyName !== null && !o.isAdminInitiated), + ), + ); this.anyActiveSponsorships$ = this.activeSponsorshipOrgs$.pipe(map((orgs) => orgs.length > 0)); this.loading = false; diff --git a/libs/common/src/admin-console/models/data/organization.data.spec.ts b/libs/common/src/admin-console/models/data/organization.data.spec.ts index fae24133502..7d66e7bc0d5 100644 --- a/libs/common/src/admin-console/models/data/organization.data.spec.ts +++ b/libs/common/src/admin-console/models/data/organization.data.spec.ts @@ -59,6 +59,7 @@ describe("ORGANIZATIONS state", () => { userIsManagedByOrganization: false, useRiskInsights: false, useAdminSponsoredFamilies: false, + isAdminInitiated: false, }, }; const result = sut.deserializer(JSON.parse(JSON.stringify(expectedResult))); diff --git a/libs/common/src/admin-console/models/data/organization.data.ts b/libs/common/src/admin-console/models/data/organization.data.ts index 799d062aefa..e0783957117 100644 --- a/libs/common/src/admin-console/models/data/organization.data.ts +++ b/libs/common/src/admin-console/models/data/organization.data.ts @@ -61,6 +61,7 @@ export class OrganizationData { userIsManagedByOrganization: boolean; useRiskInsights: boolean; useAdminSponsoredFamilies: boolean; + isAdminInitiated: boolean; constructor( response?: ProfileOrganizationResponse, @@ -124,6 +125,7 @@ export class OrganizationData { this.userIsManagedByOrganization = response.userIsManagedByOrganization; this.useRiskInsights = response.useRiskInsights; this.useAdminSponsoredFamilies = response.useAdminSponsoredFamilies; + this.isAdminInitiated = response.isAdminInitiated; this.isMember = options.isMember; this.isProviderUser = options.isProviderUser; diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts index 2e51c54b0ad..1864d56649b 100644 --- a/libs/common/src/admin-console/models/domain/organization.ts +++ b/libs/common/src/admin-console/models/domain/organization.ts @@ -91,6 +91,7 @@ export class Organization { userIsManagedByOrganization: boolean; useRiskInsights: boolean; useAdminSponsoredFamilies: boolean; + isAdminInitiated: boolean; constructor(obj?: OrganizationData) { if (obj == null) { @@ -150,6 +151,7 @@ export class Organization { this.userIsManagedByOrganization = obj.userIsManagedByOrganization; this.useRiskInsights = obj.useRiskInsights; this.useAdminSponsoredFamilies = obj.useAdminSponsoredFamilies; + this.isAdminInitiated = obj.isAdminInitiated; } get canAccess() { diff --git a/libs/common/src/admin-console/models/response/profile-organization.response.ts b/libs/common/src/admin-console/models/response/profile-organization.response.ts index da97a1034b1..3a86c764eb8 100644 --- a/libs/common/src/admin-console/models/response/profile-organization.response.ts +++ b/libs/common/src/admin-console/models/response/profile-organization.response.ts @@ -56,6 +56,7 @@ export class ProfileOrganizationResponse extends BaseResponse { userIsManagedByOrganization: boolean; useRiskInsights: boolean; useAdminSponsoredFamilies: boolean; + isAdminInitiated: boolean; constructor(response: any) { super(response); @@ -123,5 +124,6 @@ export class ProfileOrganizationResponse extends BaseResponse { this.userIsManagedByOrganization = this.getResponseProperty("UserIsManagedByOrganization"); this.useRiskInsights = this.getResponseProperty("UseRiskInsights"); this.useAdminSponsoredFamilies = this.getResponseProperty("UseAdminSponsoredFamilies"); + this.isAdminInitiated = this.getResponseProperty("IsAdminInitiated"); } } From eed18c929437ab418f35b02b866088a041a83683 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Mon, 12 May 2025 14:26:52 -0400 Subject: [PATCH 022/245] chore(view-cache): [PM-21154] Move view-cache its own feature package and adjust imports * Moved view-cache services to directory * Fixed DI for browser extension. * Fixed tests. --- .../view-cache/popup-view-cache.service.ts | 2 +- .../src/popup/services/services.module.ts | 2 +- .../vault-popup-list-filters.service.spec.ts | 5 +- .../vault-popup-list-filters.service.ts | 5 +- libs/angular/src/platform/view-cache/index.ts | 1 + .../src/platform/view-cache/internal.ts | 1 + .../noop-view-cache.service.ts | 6 +- .../src/platform/view-cache/view-cache.md | 130 ++++++++++++++++++ .../view-cache.service.ts | 2 + .../src/services/jslib-services.module.ts | 5 +- ...auth-component-email-cache.service.spec.ts | 2 +- ...auth-email-component-cache.service.spec.ts | 2 +- ...ctor-auth-email-component-cache.service.ts | 2 +- ...actor-auth-component-cache.service.spec.ts | 2 +- ...two-factor-auth-component-cache.service.ts | 2 +- ...gin-via-auth-request-cache.service.spec.ts | 2 +- ...lt-login-via-auth-request-cache.service.ts | 2 +- .../src/cipher-form/cipher-form.stories.ts | 2 +- .../components/cipher-form.component.spec.ts | 2 +- .../default-cipher-form-cache.service.spec.ts | 2 +- .../default-cipher-form-cache.service.ts | 2 +- 21 files changed, 155 insertions(+), 26 deletions(-) create mode 100644 libs/angular/src/platform/view-cache/index.ts create mode 100644 libs/angular/src/platform/view-cache/internal.ts rename libs/angular/src/platform/{services => view-cache}/noop-view-cache.service.ts (87%) create mode 100644 libs/angular/src/platform/view-cache/view-cache.md rename libs/angular/src/platform/{abstractions => view-cache}/view-cache.service.ts (98%) diff --git a/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts b/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts index ff63b52ab3f..2a946982990 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts @@ -19,7 +19,7 @@ import { FormCacheOptions, SignalCacheOptions, ViewCacheService, -} from "@bitwarden/angular/platform/abstractions/view-cache.service"; +} from "@bitwarden/angular/platform/view-cache"; import { MessageSender } from "@bitwarden/common/platform/messaging"; import { GlobalStateProvider } from "@bitwarden/common/platform/state"; diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 00b8ae81cf9..6ede88dfc13 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -5,9 +5,9 @@ import { Router } from "@angular/router"; import { merge, of, Subject } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; import { AngularThemingService } from "@bitwarden/angular/platform/services/theming/angular-theming.service"; import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { CLIENT_TYPE, DEFAULT_VAULT_TIMEOUT, diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts index 9498d953808..621ec795157 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts @@ -4,6 +4,7 @@ import { FormBuilder } from "@angular/forms"; import { BehaviorSubject, skipWhile } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -20,8 +21,6 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; -import { PopupViewCacheService } from "../../../platform/popup/view-cache/popup-view-cache.service"; - import { CachedFilterState, MY_VAULT_ID, @@ -123,7 +122,7 @@ describe("VaultPopupListFiltersService", () => { useValue: accountService, }, { - provide: PopupViewCacheService, + provide: ViewCacheService, useValue: viewCacheService, }, ], diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts index c726678c973..db4cfeefe9f 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts @@ -15,6 +15,7 @@ import { } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { DynamicTreeNode } from "@bitwarden/angular/vault/vault-filter/models/dynamic-tree-node.model"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -40,8 +41,6 @@ import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; import { ChipSelectOption } from "@bitwarden/components"; -import { PopupViewCacheService } from "../../../platform/popup/view-cache/popup-view-cache.service"; - const FILTER_VISIBILITY_KEY = new KeyDefinition(VAULT_SETTINGS_DISK, "filterVisibility", { deserializer: (obj) => obj, }); @@ -178,7 +177,7 @@ export class VaultPopupListFiltersService { private policyService: PolicyService, private stateProvider: StateProvider, private accountService: AccountService, - private viewCacheService: PopupViewCacheService, + private viewCacheService: ViewCacheService, ) { this.filterForm.controls.organization.valueChanges .pipe(takeUntilDestroyed()) diff --git a/libs/angular/src/platform/view-cache/index.ts b/libs/angular/src/platform/view-cache/index.ts new file mode 100644 index 00000000000..79deef6aa5b --- /dev/null +++ b/libs/angular/src/platform/view-cache/index.ts @@ -0,0 +1 @@ +export { ViewCacheService, FormCacheOptions, SignalCacheOptions } from "./view-cache.service"; diff --git a/libs/angular/src/platform/view-cache/internal.ts b/libs/angular/src/platform/view-cache/internal.ts new file mode 100644 index 00000000000..6e0992eecbf --- /dev/null +++ b/libs/angular/src/platform/view-cache/internal.ts @@ -0,0 +1 @@ +export { NoopViewCacheService } from "./noop-view-cache.service"; diff --git a/libs/angular/src/platform/services/noop-view-cache.service.ts b/libs/angular/src/platform/view-cache/noop-view-cache.service.ts similarity index 87% rename from libs/angular/src/platform/services/noop-view-cache.service.ts rename to libs/angular/src/platform/view-cache/noop-view-cache.service.ts index 9953e80b3b0..f83a0fc0b04 100644 --- a/libs/angular/src/platform/services/noop-view-cache.service.ts +++ b/libs/angular/src/platform/view-cache/noop-view-cache.service.ts @@ -1,11 +1,7 @@ import { Injectable, signal, WritableSignal } from "@angular/core"; import type { FormGroup } from "@angular/forms"; -import { - FormCacheOptions, - SignalCacheOptions, - ViewCacheService, -} from "../abstractions/view-cache.service"; +import { FormCacheOptions, SignalCacheOptions, ViewCacheService } from "./view-cache.service"; /** * The functionality of the {@link ViewCacheService} is only needed in the browser extension popup, diff --git a/libs/angular/src/platform/view-cache/view-cache.md b/libs/angular/src/platform/view-cache/view-cache.md new file mode 100644 index 00000000000..c1f80da5800 --- /dev/null +++ b/libs/angular/src/platform/view-cache/view-cache.md @@ -0,0 +1,130 @@ +# Extension Persistence + +By default, when the browser extension popup closes, the user's current view and any data entered +without saving is lost. This introduces friction in several workflows within our client, such as: + +- Performing actions that require email OTP entry, since the user must navigate from the popup to + get to their email inbox +- Entering information to create a new vault item from a browser tab +- And many more + +Previously, we have recommended that users "pop out" the extension into its own window to persist +the extension context, but this introduces additional user actions and may leave the extension open +(and unlocked) for longer than a user intends. + +In order to provide a better user experience, we have introduced two levels of persistence to the +Bitwarden extension client: + +- We persist the route history, allowing us to re-open the last route when the popup re-opens, and +- We offer a service for teams to use to persist component-specific form data or state to survive a + popup close/re-open cycle + +## Persistence lifetime + +Since we are persisting data, it is important that the lifetime of that data be well-understood and +well-constrained. The cache of route history and form data is cleared when any of the following +events occur: + +- The account is locked +- The account is logged out +- Account switching is used to switch the active account +- The extension popup has been closed for 2 minutes + +In addition, cached form data is cleared when a browser extension navigation event occurs (e.g. +switching between tabs in the extension). + +## Types of persistence + +### Route history persistence + +Route history is persisted on the extension automatically, with no specific implementation required +on any component. + +The persistence layer ensures that the popup will open at the same route as was active when it +closed, provided that none of the lifetime expiration events have occurred. + +:::tip Excluding a route + +If a particular route should be excluded from the history and not persisted, add +`doNotSaveUrl: true` to the `data` property on the route. + +::: + +### View data persistence + +Route persistence ensures that the user will land back on the route that they were on when the popup +closed, but it does not persist any state or form data that the user may have modified. In order to +persist that data, the component is responsible for registering that data with the +[`ViewCacheService`](./view-cache.service.ts). +This is done prescriptively to ensure that only necessary data is cached and that it is done with +intention by the component. + +The `ViewCacheService` provides an interface for caching both individual state and `FormGroup`s. + +#### Caching individual data elements + +For individual pieces of state, use the `signal()` method on the `ViewCacheService` to create a +writeable [signal](https://angular.dev/guide/signals) wrapper around the desired state. + +```typescript +const mySignal = this.viewCacheService.signal({ + key: "my-state-key" + initialValue: null +}); +``` + +If a cached value exists, the returned signal will contain the cached data. + +Setting the value should be done through the signal's `set()` method: + +```typescript +const mySignal = this.viewCacheService.signal({ + key: "my-state-key" + initialValue: null +}); +mySignal.set("value") +``` + +:::note Equality comparison + +By default, signals use `Object.is` to determine equality, and `set()` will only trigger updates if +the updated value is not equal to the current signal state. See documentation +[here](https://angular.dev/guide/signals#signal-equality-functions). + +::: + +Putting this together, the most common implementation pattern would be: + +1. **Register the signal** using `ViewCacheService.signal()` on initialization of the component or + service responsible for the state being persisted. +2. **Restore state from the signal:** If cached data exists, the signal will contain that data. The + component or service should use this data to re-create the state from prior to the popup closing. +3. **Set new state** in the cache when it changes. Ensure that any updates to the data are persisted + to the cache with `set()`, so that the cache reflects the latest state. + +#### Caching form data + +For persisting form data, the `ViewCacheService` supplies a `formGroup()` method, which manages the +persistence of any entered form data to the cache and the initialization of the form from the cached +data. You can supply the `FormGroup` in the `control` parameter of the method, and the +`ViewCacheService` will: + +- Initialize the form the a cached value, if it exists +- Save form value to cache when it changes +- Mark the form dirty if the restored value is not `undefined`. + +```typescript +this.loginDetailsForm = this.viewCacheService.formGroup({ + key: "my-form", + control: this.formBuilder.group({ + username: [""], + email: [""], + }), +}); +``` + +## What about other clients? + +The `ViewCacheService` is designed to be injected into shared, client-agnostic components. A +`NoopViewCacheService` is provided and injected for non-extension clients, preserving a single +interface for your components. diff --git a/libs/angular/src/platform/abstractions/view-cache.service.ts b/libs/angular/src/platform/view-cache/view-cache.service.ts similarity index 98% rename from libs/angular/src/platform/abstractions/view-cache.service.ts rename to libs/angular/src/platform/view-cache/view-cache.service.ts index c5ae6c77d1f..498a29aa242 100644 --- a/libs/angular/src/platform/abstractions/view-cache.service.ts +++ b/libs/angular/src/platform/view-cache/view-cache.service.ts @@ -42,6 +42,8 @@ export type FormCacheOptions = BaseCacheOptions< /** * Cache for temporary component state * + * [Read more](./view-cache.md) + * * #### Implementations * - browser extension popup: used to persist UI between popup open and close * - all other clients: noop diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 4e7c558a0f0..470115ae3f0 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -325,13 +325,14 @@ import { import { DeviceTrustToastService as DeviceTrustToastServiceAbstraction } from "../auth/services/device-trust-toast.service.abstraction"; import { DeviceTrustToastService } from "../auth/services/device-trust-toast.service.implementation"; import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service"; -import { ViewCacheService } from "../platform/abstractions/view-cache.service"; import { FormValidationErrorsService } from "../platform/services/form-validation-errors.service"; import { LoggingErrorHandler } from "../platform/services/logging-error-handler"; -import { NoopViewCacheService } from "../platform/services/noop-view-cache.service"; import { AngularThemingService } from "../platform/services/theming/angular-theming.service"; import { AbstractThemingService } from "../platform/services/theming/theming.service.abstraction"; import { safeProvider, SafeProvider } from "../platform/utils/safe-provider"; +import { ViewCacheService } from "../platform/view-cache"; +// eslint-disable-next-line no-restricted-imports -- Needed for DI +import { NoopViewCacheService } from "../platform/view-cache/internal"; import { CLIENT_TYPE, diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-component-email-cache.service.spec.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-component-email-cache.service.spec.ts index f3b904a4ea6..d2d86710b72 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-component-email-cache.service.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-component-email-cache.service.spec.ts @@ -2,7 +2,7 @@ import { TestBed } from "@angular/core/testing"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.spec.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.spec.ts index 1613c0e4af8..36d99ee56ac 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.spec.ts @@ -2,7 +2,7 @@ import { TestBed } from "@angular/core/testing"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.ts index e32b6cd1385..d274b8003d7 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.ts @@ -1,7 +1,7 @@ import { inject, Injectable, WritableSignal } from "@angular/core"; import { Jsonify } from "type-fest"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.spec.ts index 0993954fde1..5b5d486556b 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.spec.ts @@ -2,7 +2,7 @@ import { TestBed } from "@angular/core/testing"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.ts index 61b44aa98dd..2d9fcaa5633 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.ts @@ -1,7 +1,7 @@ import { inject, Injectable, WritableSignal } from "@angular/core"; import { Jsonify } from "type-fest"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; diff --git a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts index 89b78500f1f..ab82b98f614 100644 --- a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts +++ b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts @@ -1,7 +1,7 @@ import { signal } from "@angular/core"; import { TestBed } from "@angular/core/testing"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; diff --git a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts index 493fea5c14b..8fbdb925e7c 100644 --- a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts +++ b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts @@ -1,6 +1,6 @@ import { inject, Injectable, WritableSignal } from "@angular/core"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts index 9943f07292d..7090502ef14 100644 --- a/libs/vault/src/cipher-form/cipher-form.stories.ts +++ b/libs/vault/src/cipher-form/cipher-form.stories.ts @@ -13,7 +13,7 @@ import { import { BehaviorSubject } from "rxjs"; import { CollectionView } from "@bitwarden/admin-console/common"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts b/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts index 4c61ad5d2d5..da687f33ef9 100644 --- a/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts +++ b/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts @@ -3,7 +3,7 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { ReactiveFormsModule } from "@angular/forms"; import { mock } from "jest-mock-extended"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; diff --git a/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.spec.ts b/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.spec.ts index 6236e2d3dac..0dec46e1b20 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.spec.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.spec.ts @@ -1,7 +1,7 @@ import { signal } from "@angular/core"; import { TestBed } from "@angular/core/testing"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; diff --git a/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.ts index 268b2db306b..b4a8138e025 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.ts @@ -1,6 +1,6 @@ import { inject, Injectable } from "@angular/core"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; From a8fb456329ab1d5eaa32684e43dffc6a8585b829 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham Date: Mon, 12 May 2025 14:38:08 -0400 Subject: [PATCH 023/245] Uif/remove a11y manual flag (#14744) * remove comment left in error * remove runOnly options and manual flag --- .storybook/preview.tsx | 4 ---- .storybook/test-runner.ts | 5 +---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 19a4fc51dc8..a948fce0428 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -28,10 +28,6 @@ const preview: Preview = { parameters: { a11y: { element: "#storybook-root", - manual: true, - options: { - runOnly: ["section508", "wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"], - }, }, controls: { matchers: { diff --git a/.storybook/test-runner.ts b/.storybook/test-runner.ts index 8584a321090..208adf96214 100644 --- a/.storybook/test-runner.ts +++ b/.storybook/test-runner.ts @@ -1,7 +1,4 @@ -import { - // waitForPageReady, - type TestRunnerConfig, -} from "@storybook/test-runner"; +import { type TestRunnerConfig } from "@storybook/test-runner"; import { injectAxe, checkA11y } from "axe-playwright"; const testRunnerConfig: TestRunnerConfig = { From 90d8e822ff09d6c4013cc346972712e902c0f797 Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Mon, 12 May 2025 14:08:48 -0500 Subject: [PATCH 024/245] chore: remove vault-bulk-management-action flag for the third time, refs PM-13112 (#14747) --- libs/common/src/enums/feature-flag.enum.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 08de8ada25a..aa9a9ab5d6e 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -55,7 +55,6 @@ export enum FeatureFlag { /* Vault */ PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge", PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form", - VaultBulkManagementAction = "vault-bulk-management-action", SecurityTasks = "security-tasks", CipherKeyEncryption = "cipher-key-encryption", PM18520_UpdateDesktopCipherForm = "pm-18520-desktop-cipher-forms", @@ -107,7 +106,6 @@ export const DefaultFeatureFlagValue = { /* Vault */ [FeatureFlag.PM8851_BrowserOnboardingNudge]: FALSE, [FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE, - [FeatureFlag.VaultBulkManagementAction]: FALSE, [FeatureFlag.SecurityTasks]: FALSE, [FeatureFlag.CipherKeyEncryption]: FALSE, [FeatureFlag.PM18520_UpdateDesktopCipherForm]: FALSE, From ce8e123c447c8bdbbbc8a6bb0eb1381e837e4fbe Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Mon, 12 May 2025 12:17:15 -0700 Subject: [PATCH 025/245] [PM-21480] - check for privacy granted in isBrowserAutofillSettingOverridden (#14748) * check for privacy granted in isBrowserAutofillSettingOverridden * check for privacy granted in browserAutofillSettingsOverridden --- apps/browser/src/platform/browser/browser-api.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index b27e8ca7c96..4ef72fa0077 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -664,6 +664,10 @@ export class BrowserApi { * Identifies if the browser autofill settings are overridden by the extension. */ static async browserAutofillSettingsOverridden(): Promise { + if (!(await BrowserApi.permissionsGranted(["privacy"]))) { + return false; + } + const checkOverrideStatus = (details: chrome.types.ChromeSettingGetResult) => details.levelOfControl === "controlled_by_this_extension" && !details.value; From b8074a6f730c0179b71f2ccde0b06920d46d57ea Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Mon, 12 May 2025 15:18:02 -0400 Subject: [PATCH 026/245] chore(feature-flag): Remove pm-9112-device-approval-persistence (#14718) --- .../login-via-auth-request.component.ts | 34 +++++------ ...gin-via-auth-request-cache.service.spec.ts | 61 ++++--------------- ...lt-login-via-auth-request-cache.service.ts | 27 -------- libs/common/src/enums/feature-flag.enum.ts | 2 - 4 files changed, 27 insertions(+), 97 deletions(-) diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts index ab5b0c09b32..4c95a1eca3e 100644 --- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts +++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts @@ -23,12 +23,10 @@ import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request" import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view"; import { ClientType, HttpStatusCode } from "@bitwarden/common/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -101,7 +99,6 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { private validationService: ValidationService, private loginSuccessHandlerService: LoginSuccessHandlerService, private loginViaAuthRequestCacheService: LoginViaAuthRequestCacheService, - private configService: ConfigService, ) { this.clientType = this.platformUtilsService.getClientType(); @@ -132,7 +129,6 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { async ngOnInit(): Promise { // Get the authStatus early because we use it in both flows this.authStatus = await firstValueFrom(this.authService.activeAccountStatus$); - await this.loginViaAuthRequestCacheService.init(); const userHasAuthenticatedViaSSO = this.authStatus === AuthenticationStatus.Locked; @@ -410,24 +406,22 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { const authRequestResponse: AuthRequestResponse = await this.authRequestApiService.postAuthRequest(authRequest); - if (await this.configService.getFeatureFlag(FeatureFlag.PM9112_DeviceApprovalPersistence)) { - if (!this.authRequestKeyPair.privateKey) { - this.logService.error("No private key when trying to cache the login view."); - return; - } - - if (!this.accessCode) { - this.logService.error("No access code when trying to cache the login view."); - return; - } - - this.loginViaAuthRequestCacheService.cacheLoginView( - authRequestResponse.id, - this.authRequestKeyPair.privateKey, - this.accessCode, - ); + if (!this.authRequestKeyPair.privateKey) { + this.logService.error("No private key when trying to cache the login view."); + return; } + if (!this.accessCode) { + this.logService.error("No access code when trying to cache the login view."); + return; + } + + this.loginViaAuthRequestCacheService.cacheLoginView( + authRequestResponse.id, + this.authRequestKeyPair.privateKey, + this.accessCode, + ); + if (authRequestResponse.id) { await this.anonymousHubService.createHubConnection(authRequestResponse.id); } diff --git a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts index ab82b98f614..67ac605e611 100644 --- a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts +++ b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts @@ -3,7 +3,6 @@ import { TestBed } from "@angular/core/testing"; import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { LoginViaAuthRequestCacheService } from "./default-login-via-auth-request-cache.service"; @@ -14,74 +13,40 @@ describe("LoginViaAuthRequestCache", () => { const cacheSignal = signal(null); const getCacheSignal = jest.fn().mockReturnValue(cacheSignal); - const getFeatureFlag = jest.fn().mockResolvedValue(false); const cacheSetMock = jest.spyOn(cacheSignal, "set"); beforeEach(() => { getCacheSignal.mockClear(); - getFeatureFlag.mockClear(); cacheSetMock.mockClear(); testBed = TestBed.configureTestingModule({ providers: [ { provide: ViewCacheService, useValue: { signal: getCacheSignal } }, - { provide: ConfigService, useValue: { getFeatureFlag } }, LoginViaAuthRequestCacheService, ], }); }); - describe("feature enabled", () => { - beforeEach(() => { - getFeatureFlag.mockResolvedValue(true); - }); + it("`getCachedLoginViaAuthRequestView` returns the cached data", async () => { + cacheSignal.set({ ...buildMockState() }); + service = testBed.inject(LoginViaAuthRequestCacheService); - it("`getCachedLoginViaAuthRequestView` returns the cached data", async () => { - cacheSignal.set({ ...buildMockState() }); - service = testBed.inject(LoginViaAuthRequestCacheService); - await service.init(); - - expect(service.getCachedLoginViaAuthRequestView()).toEqual({ - ...buildMockState(), - }); - }); - - it("updates the signal value", async () => { - service = testBed.inject(LoginViaAuthRequestCacheService); - await service.init(); - - const parameters = buildAuthenticMockAuthView(); - - service.cacheLoginView(parameters.id, parameters.privateKey, parameters.accessCode); - - expect(cacheSignal.set).toHaveBeenCalledWith({ - id: parameters.id, - privateKey: Utils.fromBufferToB64(parameters.privateKey), - accessCode: parameters.accessCode, - }); + expect(service.getCachedLoginViaAuthRequestView()).toEqual({ + ...buildMockState(), }); }); - describe("feature disabled", () => { - beforeEach(async () => { - cacheSignal.set({ ...buildMockState() } as LoginViaAuthRequestView); - getFeatureFlag.mockResolvedValue(false); - cacheSetMock.mockClear(); + it("updates the signal value", async () => { + service = testBed.inject(LoginViaAuthRequestCacheService); - service = testBed.inject(LoginViaAuthRequestCacheService); - await service.init(); - }); + const parameters = buildAuthenticMockAuthView(); - it("`getCachedCipherView` returns null", () => { - expect(service.getCachedLoginViaAuthRequestView()).toBeNull(); - }); + service.cacheLoginView(parameters.id, parameters.privateKey, parameters.accessCode); - it("does not update the signal value", () => { - const params = buildAuthenticMockAuthView(); - - service.cacheLoginView(params.id, params.privateKey, params.accessCode); - - expect(cacheSignal.set).not.toHaveBeenCalled(); + expect(cacheSignal.set).toHaveBeenCalledWith({ + id: parameters.id, + privateKey: Utils.fromBufferToB64(parameters.privateKey), + accessCode: parameters.accessCode, }); }); diff --git a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts index 8fbdb925e7c..80dbafd3159 100644 --- a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts +++ b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts @@ -2,8 +2,6 @@ import { inject, Injectable, WritableSignal } from "@angular/core"; import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; const LOGIN_VIA_AUTH_CACHE_KEY = "login-via-auth-request-form-cache"; @@ -17,10 +15,6 @@ const LOGIN_VIA_AUTH_CACHE_KEY = "login-via-auth-request-form-cache"; @Injectable() export class LoginViaAuthRequestCacheService { private viewCacheService: ViewCacheService = inject(ViewCacheService); - private configService: ConfigService = inject(ConfigService); - - /** True when the `PM9112_DeviceApproval` flag is enabled */ - private featureEnabled: boolean = false; private defaultLoginViaAuthRequestCache: WritableSignal = this.viewCacheService.signal({ @@ -31,23 +25,10 @@ export class LoginViaAuthRequestCacheService { constructor() {} - /** - * Must be called once before interacting with the cached data, otherwise methods will be noop. - */ - async init() { - this.featureEnabled = await this.configService.getFeatureFlag( - FeatureFlag.PM9112_DeviceApprovalPersistence, - ); - } - /** * Update the cache with the new LoginView. */ cacheLoginView(id: string, privateKey: Uint8Array, accessCode: string): void { - if (!this.featureEnabled) { - return; - } - // When the keys get stored they should be converted to a B64 string to ensure // data can be properly formed when json-ified. If not done, they are not stored properly and // will not be parsable by the cryptography library after coming out of storage. @@ -59,10 +40,6 @@ export class LoginViaAuthRequestCacheService { } clearCacheLoginView(): void { - if (!this.featureEnabled) { - return; - } - this.defaultLoginViaAuthRequestCache.set(null); } @@ -70,10 +47,6 @@ export class LoginViaAuthRequestCacheService { * Returns the cached LoginViaAuthRequestView when available. */ getCachedLoginViaAuthRequestView(): LoginViaAuthRequestView | null { - if (!this.featureEnabled) { - return null; - } - return this.defaultLoginViaAuthRequestCache(); } } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index aa9a9ab5d6e..a7679a6248a 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -16,7 +16,6 @@ export enum FeatureFlag { SeparateCustomRolePermissions = "pm-19917-separate-custom-role-permissions", /* Auth */ - PM9112_DeviceApprovalPersistence = "pm-9112-device-approval-persistence", PM9115_TwoFactorExtensionDataPersistence = "pm-9115-two-factor-extension-data-persistence", /* Autofill */ @@ -112,7 +111,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.EndUserNotifications]: FALSE, /* Auth */ - [FeatureFlag.PM9112_DeviceApprovalPersistence]: FALSE, [FeatureFlag.PM9115_TwoFactorExtensionDataPersistence]: FALSE, /* Billing */ From 7d01c42e37e07d6642a2fbda5eb27cc12fdf4f85 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 15:42:21 -0400 Subject: [PATCH 027/245] [deps] Vault: Update multer to v1.4.5-lts.2 (#14469) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: SmithThe4th --- apps/cli/package.json | 2 +- package-lock.json | 10 +++++----- package.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 79d4786a23c..0a7e66d7818 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -81,7 +81,7 @@ "koa-json": "2.0.2", "lowdb": "1.0.0", "lunr": "2.3.9", - "multer": "1.4.5-lts.1", + "multer": "1.4.5-lts.2", "node-fetch": "2.6.12", "node-forge": "1.3.1", "open": "8.4.2", diff --git a/package-lock.json b/package-lock.json index 81c9384fbbb..44bebc9cbd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,7 @@ "lit": "3.2.1", "lowdb": "1.0.0", "lunr": "2.3.9", - "multer": "1.4.5-lts.1", + "multer": "1.4.5-lts.2", "ngx-toastr": "19.0.0", "node-fetch": "2.6.12", "node-forge": "1.3.1", @@ -217,7 +217,7 @@ "koa-json": "2.0.2", "lowdb": "1.0.0", "lunr": "2.3.9", - "multer": "1.4.5-lts.1", + "multer": "1.4.5-lts.2", "node-fetch": "2.6.12", "node-forge": "1.3.1", "open": "8.4.2", @@ -28598,9 +28598,9 @@ } }, "node_modules/multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", diff --git a/package.json b/package.json index 8ba1c95df6a..47cede513ed 100644 --- a/package.json +++ b/package.json @@ -190,7 +190,7 @@ "lit": "3.2.1", "lowdb": "1.0.0", "lunr": "2.3.9", - "multer": "1.4.5-lts.1", + "multer": "1.4.5-lts.2", "ngx-toastr": "19.0.0", "node-fetch": "2.6.12", "node-forge": "1.3.1", From 9af7963609a08a5e00dd209e5614022c0d58350a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 14:58:48 -0500 Subject: [PATCH 028/245] [deps] Vault: Update @koa/multer to v3.1.0 (#14495) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> --- apps/cli/package.json | 2 +- package-lock.json | 68 ++++++++++++++++++++----------------------- package.json | 2 +- 3 files changed, 33 insertions(+), 39 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 0a7e66d7818..82faa7d40e6 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -64,7 +64,7 @@ ] }, "dependencies": { - "@koa/multer": "3.0.2", + "@koa/multer": "3.1.0", "@koa/router": "13.1.0", "argon2": "0.41.1", "big-integer": "1.6.52", diff --git a/package-lock.json b/package-lock.json index 44bebc9cbd4..8466a4ba4c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@bitwarden/sdk-internal": "0.2.0-main.159", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", - "@koa/multer": "3.0.2", + "@koa/multer": "3.1.0", "@koa/router": "13.1.0", "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", @@ -200,7 +200,7 @@ "version": "2025.4.0", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { - "@koa/multer": "3.0.2", + "@koa/multer": "3.1.0", "@koa/router": "13.1.0", "argon2": "0.41.1", "big-integer": "1.6.52", @@ -389,6 +389,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -2745,6 +2746,7 @@ "version": "7.26.8", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2754,6 +2756,7 @@ "version": "7.24.9", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", + "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -2784,12 +2787,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, "license": "MIT" }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2799,6 +2804,7 @@ "version": "7.26.10", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz", "integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.26.10", @@ -2828,6 +2834,7 @@ "version": "7.26.5", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.26.5", @@ -2844,6 +2851,7 @@ "version": "4.24.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, "funding": [ { "type": "opencollective", @@ -2876,6 +2884,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3015,6 +3024,7 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.25.9", @@ -3045,6 +3055,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3148,6 +3159,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3172,6 +3184,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.1", @@ -3280,23 +3293,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", @@ -3382,6 +3378,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.3" @@ -4010,6 +4007,7 @@ "version": "7.26.3", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.26.0", @@ -7855,15 +7853,12 @@ } }, "node_modules/@koa/multer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@koa/multer/-/multer-3.0.2.tgz", - "integrity": "sha512-Q6WfPpE06mJWyZD1fzxM6zWywaoo+zocAn2YA9QYz4RsecoASr1h/kSzG0c5seDpFVKCMZM9raEfuM7XfqbRLw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@koa/multer/-/multer-3.1.0.tgz", + "integrity": "sha512-ETf4OLpOew9XE9lyU+5HIqk3TCmdGAw9pUXgxzrlYip+PkxLGoU4meiVTxiW4B6lxdBNijb3DFQ7M2woLcDL1g==", "license": "MIT", - "dependencies": { - "fix-esm": "1.0.1" - }, "engines": { - "node": ">= 8" + "node": ">= 14" }, "peerDependencies": { "multer": "*" @@ -15362,6 +15357,7 @@ "version": "4.23.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "dev": true, "funding": [ { "type": "opencollective", @@ -15951,6 +15947,7 @@ "version": "1.0.30001717", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz", "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -18482,6 +18479,7 @@ "version": "1.5.151", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz", "integrity": "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==", + "dev": true, "license": "ISC" }, "node_modules/electron-updater": { @@ -18965,6 +18963,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -20387,17 +20386,6 @@ "micromatch": "^4.0.2" } }, - "node_modules/fix-esm": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fix-esm/-/fix-esm-1.0.1.tgz", - "integrity": "sha512-EZtb7wPXZS54GaGxaWxMlhd1DUDCnAg5srlYdu/1ZVeW+7wwR3Tp59nu52dXByFs3MBRq+SByx1wDOJpRvLEXw==", - "license": "WTFPL OR CC0-1.0", - "dependencies": { - "@babel/core": "^7.14.6", - "@babel/plugin-proposal-export-namespace-from": "^7.14.5", - "@babel/plugin-transform-modules-commonjs": "^7.14.5" - } - }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -21084,6 +21072,7 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -25452,6 +25441,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -27032,6 +27022,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -29278,6 +29269,7 @@ "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, "license": "MIT" }, "node_modules/nopt": { @@ -37088,6 +37080,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -38970,6 +38963,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, "license": "ISC" }, "node_modules/yaml": { diff --git a/package.json b/package.json index 47cede513ed..2993707313f 100644 --- a/package.json +++ b/package.json @@ -163,7 +163,7 @@ "@bitwarden/sdk-internal": "0.2.0-main.159", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", - "@koa/multer": "3.0.2", + "@koa/multer": "3.1.0", "@koa/router": "13.1.0", "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", From 63b224f3dd08f692c14e18109fba4463062b5c63 Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Mon, 12 May 2025 17:03:09 -0400 Subject: [PATCH 029/245] PM-21454 Firefox - "null" text is showing in Add/Update Confirmation notification (#14745) * PM-21454 * Update apps/browser/src/autofill/content/components/lit-stories/mock-data.ts Co-authored-by: Jonathan Prusik * Update apps/browser/src/autofill/content/components/lit-stories/mock-data.ts Co-authored-by: Jonathan Prusik * Update apps/browser/src/autofill/content/components/notification/confirmation/container.ts Co-authored-by: Jonathan Prusik * run lint --------- Co-authored-by: Jonathan Prusik --- apps/browser/src/_locales/en/messages.json | 19 ++---- .../components/lit-stories/mock-data.ts | 4 +- .../notification/confirmation/container.ts | 9 ++- .../notification/confirmation/message.ts | 63 ++++++++++++------- apps/browser/src/autofill/notification/bar.ts | 6 +- 5 files changed, 57 insertions(+), 44 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 57a0e78fd1b..361093b12ef 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1088,22 +1088,13 @@ } } }, - "loginSaveConfirmation": { - "message": "$ITEMNAME$ saved to Bitwarden.", - "placeholders": { - "itemName": { - "content": "$1" - } - }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." }, - "loginUpdatedConfirmation": { - "message": "$ITEMNAME$ updated in Bitwarden.", - "placeholders": { - "itemName": { - "content": "$1" - } - }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { diff --git a/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts b/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts index b8e35813c6c..d134db3ca6f 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts +++ b/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts @@ -107,9 +107,9 @@ export const mockI18n = { collection: "Collection", folder: "Folder", loginSaveSuccess: "Login saved", - loginSaveConfirmation: "$ITEMNAME$ saved to Bitwarden.", + notificationLoginSaveConfirmation: "saved to Bitwarden.", loginUpdateSuccess: "Login updated", - loginUpdatedConfirmation: "$ITEMNAME$ updated in Bitwarden.", + notificationLoginUpdatedConfirmation: "updated in Bitwarden.", loginUpdateTaskSuccess: "Great job! You took the steps to make you and $ORGANIZATION$ more secure.", loginUpdateTaskSuccessAdditional: diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/container.ts b/apps/browser/src/autofill/content/components/notification/confirmation/container.ts index 7ad6b4542ec..d4d66c7a7be 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/container.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/container.ts @@ -113,9 +113,14 @@ function getConfirmationMessage(i18n: I18n, type?: NotificationType, error?: str if (error) { return i18n.saveFailureDetails; } + + /* @TODO This partial string return and later concatenation with the cipher name is needed + * to handle cipher name overflow cases, but is risky for i18n concerns. Fix concatenation + * with cipher name overflow when a tag replacement solution is available. + */ return type === NotificationTypes.Add - ? i18n.loginSaveConfirmation - : i18n.loginUpdatedConfirmation; + ? i18n.notificationLoginSaveConfirmation + : i18n.notificationLoginUpdatedConfirmation; } function getHeaderMessage(i18n: I18n, type?: NotificationType, error?: string) { diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts index 527119aed15..2bf8caecfff 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts @@ -28,28 +28,31 @@ export function NotificationConfirmationMessage({

@@ -90,72 +151,6 @@ class KitchenSinkDialog { - - - - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

- - What did foo say to bar? - - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-
-
`, }) export class KitchenSinkMainComponent { @@ -168,7 +163,7 @@ export class KitchenSinkMainComponent { } openDrawer() { - this.drawerOpen.set(true); + this.dialogService.openDrawer(KitchenSinkDialog); } navItems = [ diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts index f57a9de4e68..d318e1b5f0e 100644 --- a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts +++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts @@ -14,7 +14,6 @@ import { import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { DialogService } from "../../dialog"; import { LayoutComponent } from "../../layout"; import { I18nMockService } from "../../utils/i18n-mock.service"; import { positionFixedWrapperDecorator } from "../storybook-decorators"; @@ -39,8 +38,20 @@ export default { KitchenSinkTable, KitchenSinkToggleList, ], + }), + applicationConfig({ providers: [ - DialogService, + provideNoopAnimations(), + importProvidersFrom( + RouterModule.forRoot( + [ + { path: "", redirectTo: "bitwarden", pathMatch: "full" }, + { path: "bitwarden", component: KitchenSinkMainComponent }, + { path: "virtual-scroll", component: DialogVirtualScrollBlockComponent }, + ], + { useHash: true }, + ), + ), { provide: I18nService, useFactory: () => { @@ -58,21 +69,6 @@ export default { }, ], }), - applicationConfig({ - providers: [ - provideNoopAnimations(), - importProvidersFrom( - RouterModule.forRoot( - [ - { path: "", redirectTo: "bitwarden", pathMatch: "full" }, - { path: "bitwarden", component: KitchenSinkMainComponent }, - { path: "virtual-scroll", component: DialogVirtualScrollBlockComponent }, - ], - { useHash: true }, - ), - ), - ], - }), ], } as Meta; diff --git a/libs/components/src/table/table-scroll.component.html b/libs/components/src/table/table-scroll.component.html index 8f2c88ba3ad..523912cd7ac 100644 --- a/libs/components/src/table/table-scroll.component.html +++ b/libs/components/src/table/table-scroll.component.html @@ -1,5 +1,5 @@ diff --git a/libs/components/src/table/table-scroll.component.ts b/libs/components/src/table/table-scroll.component.ts index 34cd8c5d9ca..e198da1aba0 100644 --- a/libs/components/src/table/table-scroll.component.ts +++ b/libs/components/src/table/table-scroll.component.ts @@ -21,6 +21,8 @@ import { TrackByFunction, } from "@angular/core"; +import { ScrollLayoutDirective } from "../layout"; + import { RowDirective } from "./row.directive"; import { TableComponent } from "./table.component"; @@ -58,6 +60,7 @@ export class BitRowDef { CdkFixedSizeVirtualScroll, CdkVirtualForOf, RowDirective, + ScrollLayoutDirective, ], }) export class TableScrollComponent diff --git a/libs/components/src/table/table.mdx b/libs/components/src/table/table.mdx index 8d784190ed9..59bf5b773a3 100644 --- a/libs/components/src/table/table.mdx +++ b/libs/components/src/table/table.mdx @@ -142,7 +142,7 @@ dataSource.filter = (data) => data.orgType === "family"; Rudimentary string filtering is supported out of the box with `TableDataSource.simpleStringFilter`. It works by converting each entry into a string of it's properties. The provided string is then -compared against the filter value using a simple `indexOf` check. For convienence, you can also just +compared against the filter value using a simple `indexOf` check. For convenience, you can also just pass a string directly. ```ts @@ -153,7 +153,7 @@ dataSource.filter = "search value"; ### Virtual Scrolling -It's heavily adviced to use virtual scrolling if you expect the table to have any significant amount +It's heavily advised to use virtual scrolling if you expect the table to have any significant amount of data. This is done by using the `bit-table-scroll` component instead of the `bit-table` component. This component behaves slightly different from the `bit-table` component. Instead of using the `*ngFor` directive to render the rows, you provide a `bitRowDef` template that will be @@ -178,6 +178,14 @@ height and align vertically. ``` +#### Deprecated approach + +Before `bit-table-scroll` was introduced, virtual scroll in tables was implemented manually via +constructs from Angular CDK. This included wrapping the table with a `cdk-virtual-scroll-viewport` +and targeting with `bit-layout`'s scroll container with the `bitScrollLayout` directive. + +This pattern is deprecated in favor of `bit-table-scroll`. + ## Accessibility - Always include a row or column header with your table; this allows assistive technology to better diff --git a/libs/components/src/table/table.stories.ts b/libs/components/src/table/table.stories.ts index e8ab24ee8b7..d696e6077dd 100644 --- a/libs/components/src/table/table.stories.ts +++ b/libs/components/src/table/table.stories.ts @@ -1,6 +1,13 @@ +import { RouterTestingModule } from "@angular/router/testing"; import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + import { countries } from "../form/countries"; +import { LayoutComponent } from "../layout"; +import { mockLayoutI18n } from "../layout/mocks"; +import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; +import { I18nMockService } from "../utils"; import { TableDataSource } from "./table-data-source"; import { TableModule } from "./table.module"; @@ -8,8 +15,17 @@ import { TableModule } from "./table.module"; export default { title: "Component Library/Table", decorators: [ + positionFixedWrapperDecorator(), moduleMetadata({ - imports: [TableModule], + imports: [TableModule, LayoutComponent, RouterTestingModule], + providers: [ + { + provide: I18nService, + useFactory: () => { + return new I18nMockService(mockLayoutI18n); + }, + }, + ], }), ], argTypes: { @@ -116,18 +132,20 @@ export const Scrollable: Story = { trackBy: (index: number, item: any) => item.id, }, template: ` - - - Id - Name - Other - - - {{ row.id }} - {{ row.name }} - {{ row.other }} - - + + + + Id + Name + Other + + + {{ row.id }} + {{ row.name }} + {{ row.other }} + + + `, }), }; @@ -144,17 +162,19 @@ export const Filterable: Story = { sortFn: (a: any, b: any) => a.id - b.id, }, template: ` - - - - Name - Value - - - {{ row.name }} - {{ row.value }} - - + + + + + Name + Value + + + {{ row.name }} + {{ row.value }} + + + `, }), }; diff --git a/libs/components/src/utils/has-scrolled-from.ts b/libs/components/src/utils/has-scrolled-from.ts new file mode 100644 index 00000000000..44c73465bdd --- /dev/null +++ b/libs/components/src/utils/has-scrolled-from.ts @@ -0,0 +1,41 @@ +import { CdkScrollable } from "@angular/cdk/scrolling"; +import { Signal, inject, signal } from "@angular/core"; +import { toObservable, toSignal } from "@angular/core/rxjs-interop"; +import { map, startWith, switchMap } from "rxjs"; + +export type ScrollState = { + /** `true` when the scrollbar is not at the top-most position */ + top: boolean; + + /** `true` when the scrollbar is not at the bottom-most position */ + bottom: boolean; +}; + +/** + * Check if a `CdkScrollable` instance has been scrolled + * @param scrollable The instance to check, defaults to the one provided by the current injector + * @returns {Signal} + */ +export const hasScrolledFrom = (scrollable?: Signal): Signal => { + const _scrollable = scrollable ?? signal(inject(CdkScrollable)); + const scrollable$ = toObservable(_scrollable); + + const scrollState$ = scrollable$.pipe( + switchMap((_scrollable) => + _scrollable.elementScrolled().pipe( + startWith(null), + map(() => ({ + top: _scrollable.measureScrollOffset("top") > 0, + bottom: _scrollable.measureScrollOffset("bottom") > 0, + })), + ), + ), + ); + + return toSignal(scrollState$, { + initialValue: { + top: false, + bottom: false, + }, + }); +}; From 623deea4fcf6543e34b95950ee2b5d6eb0893e56 Mon Sep 17 00:00:00 2001 From: Tom <144813356+ttalty@users.noreply.github.com> Date: Thu, 15 May 2025 10:36:57 -0400 Subject: [PATCH 065/245] Adding at risk cipher ids for accurate notifications (#14784) --- .../src/dirt/reports/risk-insights/models/password-health.ts | 1 + .../risk-insights/services/risk-insights-report.service.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts index 27e135667b4..d24d8386ecd 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts @@ -27,6 +27,7 @@ export type ApplicationHealthReportDetail = { applicationName: string; passwordCount: number; atRiskPasswordCount: number; + atRiskCipherIds: string[]; memberCount: number; atRiskMemberCount: number; memberDetails: MemberDetailsFlat[]; diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts index a503d24cad7..e4fece801b6 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts @@ -354,12 +354,15 @@ export class RiskInsightsReportService { : newUriDetail.cipherMembers, atRiskMemberDetails: existingUriDetail ? existingUriDetail.atRiskMemberDetails : [], atRiskPasswordCount: existingUriDetail ? existingUriDetail.atRiskPasswordCount : 0, + atRiskCipherIds: existingUriDetail ? existingUriDetail.atRiskCipherIds : [], atRiskMemberCount: existingUriDetail ? existingUriDetail.atRiskMemberDetails.length : 0, cipher: newUriDetail.cipher, } as ApplicationHealthReportDetail; if (isAtRisk) { reportDetail.atRiskPasswordCount = reportDetail.atRiskPasswordCount + 1; + reportDetail.atRiskCipherIds.push(newUriDetail.cipherId); + reportDetail.atRiskMemberDetails = this.getUniqueMembers( reportDetail.atRiskMemberDetails.concat(newUriDetail.cipherMembers), ); From ac49e594c1957734bfc14b5d0c04738d501d65f9 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 15 May 2025 16:44:07 +0200 Subject: [PATCH 066/245] Add standalone false to all non migrated (#14797) Adds standalone: false to all components since Angular is changing the default to true and we'd rather not have the angular PR change 300+ files. --- .../extension-anon-layout-wrapper.stories.ts | 4 ++++ apps/browser/src/auth/popup/set-password.component.ts | 1 + .../popup/settings/vault-timeout-input.component.ts | 1 + .../src/auth/popup/update-temp-password.component.ts | 1 + .../key-connector/remove-password.component.ts | 1 + .../popup/view-cache/popup-router-cache.spec.ts | 5 ++++- .../platform/popup/view-cache/popup-view-cache.spec.ts | 10 ++++++++-- apps/browser/src/popup/app.component.ts | 1 + .../popup/components/user-verification.component.ts | 1 + apps/browser/src/popup/tabs-v2.component.ts | 1 + apps/desktop/src/app/accounts/settings.component.ts | 1 + .../src/app/accounts/vault-timeout-input.component.ts | 1 + apps/desktop/src/app/app.component.ts | 1 + apps/desktop/src/app/components/avatar.component.ts | 1 + .../src/app/layout/account-switcher.component.ts | 1 + apps/desktop/src/app/layout/header.component.ts | 1 + apps/desktop/src/app/layout/search/search.component.ts | 1 + apps/desktop/src/auth/set-password.component.ts | 1 + .../desktop/src/auth/update-temp-password.component.ts | 1 + .../src/billing/app/accounts/premium.component.ts | 1 + .../key-connector/remove-password.component.ts | 1 + .../app/vault/add-edit-custom-fields.component.ts | 1 + apps/desktop/src/vault/app/vault/add-edit.component.ts | 1 + .../src/vault/app/vault/attachments.component.ts | 1 + .../src/vault/app/vault/collections.component.ts | 1 + .../src/vault/app/vault/folder-add-edit.component.ts | 1 + .../src/vault/app/vault/password-history.component.ts | 1 + apps/desktop/src/vault/app/vault/share.component.ts | 1 + .../filters/collection-filter.component.ts | 1 + .../vault-filter/filters/folder-filter.component.ts | 1 + .../filters/organization-filter.component.ts | 1 + .../vault-filter/filters/status-filter.component.ts | 1 + .../vault-filter/filters/type-filter.component.ts | 1 + .../app/vault/vault-filter/vault-filter.component.ts | 1 + .../src/vault/app/vault/vault-items.component.ts | 1 + apps/desktop/src/vault/app/vault/vault.component.ts | 1 + .../vault/app/vault/view-custom-fields.component.ts | 1 + apps/desktop/src/vault/app/vault/view.component.ts | 1 + .../group-badge/group-name-badge.component.ts | 1 + .../collections/vault-filter/vault-filter.component.ts | 1 + .../create/organization-information.component.ts | 1 + .../guards/is-enterprise-org.guard.spec.ts | 3 +++ .../organizations/guards/is-paid-org.guard.spec.ts | 3 +++ .../organizations/guards/org-redirect.guard.spec.ts | 3 +++ .../organizations/manage/events.component.ts | 1 + .../organizations/manage/group-add-edit.component.ts | 1 + .../organizations/manage/groups.component.ts | 1 + .../organizations/manage/user-confirm.component.ts | 1 + .../components/bulk/bulk-confirm-dialog.component.ts | 1 + .../components/bulk/bulk-delete-dialog.component.ts | 1 + .../components/bulk/bulk-enable-sm-dialog.component.ts | 1 + .../components/bulk/bulk-remove-dialog.component.ts | 1 + .../components/bulk/bulk-restore-revoke.component.ts | 1 + .../members/components/bulk/bulk-status.component.ts | 1 + .../member-dialog/member-dialog.component.ts | 1 + .../member-dialog/nested-checkbox.component.ts | 1 + .../members/components/reset-password.component.ts | 1 + .../organizations/members/members.component.ts | 1 + .../organizations/policies/disable-send.component.ts | 1 + .../policies/master-password.component.ts | 1 + .../policies/password-generator.component.ts | 1 + .../policies/personal-ownership.component.ts | 1 + .../organizations/policies/policies.component.ts | 1 + .../organizations/policies/policy-edit.component.ts | 1 + .../policies/remove-unlock-with-pin.component.ts | 1 + .../organizations/policies/require-sso.component.ts | 1 + .../organizations/policies/reset-password.component.ts | 1 + .../organizations/policies/send-options.component.ts | 1 + .../organizations/policies/single-org.component.ts | 1 + .../policies/two-factor-authentication.component.ts | 1 + .../organizations/reporting/reports-home.component.ts | 1 + .../organizations/settings/account.component.ts | 1 + .../settings/two-factor-setup.component.ts | 1 + .../access-selector/access-selector.component.ts | 1 + .../components/access-selector/user-type.pipe.ts | 1 + .../accept-family-sponsorship.component.ts | 1 + apps/web/src/app/app.component.ts | 1 + apps/web/src/app/auth/guards/deep-link.guard.spec.ts | 3 +++ .../login-via-webauthn/login-via-webauthn.component.ts | 1 + .../accept-organization.component.ts | 1 + apps/web/src/app/auth/recover-delete.component.ts | 1 + apps/web/src/app/auth/recover-two-factor.component.ts | 1 + apps/web/src/app/auth/set-password.component.ts | 1 + .../src/app/auth/settings/account/account.component.ts | 1 + .../settings/account/change-avatar-dialog.component.ts | 1 + .../auth/settings/account/change-email.component.ts | 1 + .../settings/account/deauthorize-sessions.component.ts | 1 + .../account/delete-account-dialog.component.ts | 1 + .../src/app/auth/settings/account/profile.component.ts | 1 + .../settings/account/selectable-avatar.component.ts | 1 + .../src/app/auth/settings/change-password.component.ts | 1 + .../confirm/emergency-access-confirm.component.ts | 1 + .../emergency-access-add-edit.component.ts | 1 + .../emergency-access/emergency-access.component.ts | 1 + .../takeover/emergency-access-takeover.component.ts | 1 + .../view/emergency-access-view.component.ts | 1 + .../app/auth/settings/security/api-key.component.ts | 1 + .../change-kdf/change-kdf-confirmation.component.ts | 1 + .../security/change-kdf/change-kdf.component.ts | 1 + .../auth/settings/security/security-keys.component.ts | 1 + .../app/auth/settings/security/security.component.ts | 1 + .../create-credential-dialog.component.ts | 1 + .../delete-credential-dialog.component.ts | 1 + .../enable-encryption-dialog.component.ts | 1 + .../webauthn-login-settings.component.ts | 1 + .../user-verification-prompt.component.ts | 1 + .../user-verification/user-verification.component.ts | 1 + apps/web/src/app/auth/update-password.component.ts | 1 + .../web/src/app/auth/update-temp-password.component.ts | 1 + apps/web/src/app/auth/verify-email-token.component.ts | 1 + .../src/app/auth/verify-recover-delete.component.ts | 1 + .../individual/billing-history-view.component.ts | 1 + .../billing/individual/premium/premium.component.ts | 1 + .../app/billing/individual/subscription.component.ts | 1 + .../billing/individual/user-subscription.component.ts | 1 + .../members/free-bitwarden-families.component.ts | 1 + .../organizations/adjust-subscription.component.ts | 1 + .../organizations/billing-sync-api-key.component.ts | 1 + .../organizations/billing-sync-key.component.ts | 1 + .../app/billing/organizations/change-plan.component.ts | 1 + .../organizations/download-license.component.ts | 1 + .../organization-billing-history-view.component.ts | 1 + .../organization-subscription-cloud.component.ts | 1 + .../organization-subscription-selfhost.component.ts | 1 + .../organization-payment-method.component.ts | 1 + .../organizations/sm-adjust-subscription.component.ts | 1 + .../organizations/sm-subscribe-standalone.component.ts | 1 + .../organizations/subscription-hidden.component.ts | 1 + .../organizations/subscription-status.component.ts | 1 + .../billing/settings/sponsored-families.component.ts | 1 + .../billing/settings/sponsoring-org-row.component.ts | 1 + .../app/billing/shared/add-credit-dialog.component.ts | 1 + .../adjust-payment-dialog.component.ts | 1 + .../adjust-storage-dialog.component.ts | 1 + .../app/billing/shared/billing-history.component.ts | 1 + .../app/billing/shared/offboarding-survey.component.ts | 1 + .../src/app/billing/shared/payment-method.component.ts | 1 + ...dividual-self-hosting-license-uploader.component.ts | 1 + ...nization-self-hosting-license-uploader.component.ts | 1 + .../src/app/billing/shared/sm-subscribe.component.ts | 1 + .../billing/shared/update-license-dialog.component.ts | 1 + .../src/app/billing/shared/update-license.component.ts | 1 + .../complete-trial-initiation.component.ts | 1 + .../trial-initiation/confirmation-details.component.ts | 1 + .../vertical-step-content.component.ts | 1 + .../vertical-stepper/vertical-step.component.ts | 1 + .../vertical-stepper/vertical-stepper.component.ts | 1 + .../environment-selector.component.ts | 1 + .../app/dirt/reports/pages/breach-report.component.ts | 1 + .../pages/exposed-passwords-report.component.ts | 1 + .../pages/inactive-two-factor-report.component.ts | 1 + .../exposed-passwords-report.component.ts | 1 + .../inactive-two-factor-report.component.ts | 1 + .../organizations/reused-passwords-report.component.ts | 1 + .../unsecured-websites-report.component.ts | 1 + .../organizations/weak-passwords-report.component.ts | 1 + .../app/dirt/reports/pages/reports-home.component.ts | 1 + .../reports/pages/reused-passwords-report.component.ts | 1 + .../pages/unsecured-websites-report.component.ts | 1 + .../reports/pages/weak-passwords-report.component.ts | 1 + .../src/app/dirt/reports/reports-layout.component.ts | 1 + .../shared/report-card/report-card.component.ts | 1 + .../shared/report-list/report-list.component.ts | 1 + .../key-connector/remove-password.component.ts | 1 + apps/web/src/app/layouts/frontend-layout.component.ts | 1 + .../web/src/app/layouts/header/web-header.component.ts | 1 + .../navigation-switcher.component.ts | 1 + .../navigation-switcher/navigation-switcher.stories.ts | 4 ++++ .../product-switcher-content.component.ts | 1 + .../product-switcher/product-switcher.component.ts | 1 + .../product-switcher/product-switcher.stories.ts | 4 ++++ apps/web/src/app/settings/domain-rules.component.ts | 1 + apps/web/src/app/settings/preferences.component.ts | 1 + .../components/onboarding/onboarding-task.component.ts | 1 + .../components/onboarding/onboarding.component.ts | 1 + .../app/vault/components/premium-badge.component.ts | 1 + .../vault-items/vault-cipher-row.component.ts | 1 + .../vault-items/vault-collection-row.component.ts | 1 + .../components/vault-items/vault-items.component.ts | 3 +-- .../bulk-delete-dialog/bulk-delete-dialog.component.ts | 1 + .../bulk-move-dialog/bulk-move-dialog.component.ts | 1 + .../individual-vault/folder-add-edit.component.ts | 1 + .../organization-name-badge.component.ts | 1 + .../individual-vault/pipes/get-group-name.pipe.ts | 1 + .../pipes/get-organization-name.pipe.ts | 1 + .../components/organization-options.component.ts | 1 + .../vault-filter/components/vault-filter.component.ts | 1 + .../components/vault-filter-section.component.ts | 1 + .../src/app/vault/settings/purge-vault.component.ts | 1 + .../domain-add-edit-dialog.component.ts | 1 + .../domain-verification.component.ts | 1 + .../organizations/manage/scim.component.ts | 1 + .../policies/activate-autofill.component.ts | 1 + .../policies/automatic-app-login.component.ts | 1 + .../disable-personal-vault-export.component.ts | 1 + .../policies/maximum-vault-timeout.component.ts | 1 + .../providers/clients/add-organization.component.ts | 1 + .../providers/clients/create-organization.component.ts | 1 + .../providers/manage/accept-provider.component.ts | 1 + .../manage/dialogs/add-edit-member-dialog.component.ts | 1 + .../manage/dialogs/bulk-confirm-dialog.component.ts | 1 + .../manage/dialogs/bulk-remove-dialog.component.ts | 1 + .../admin-console/providers/manage/events.component.ts | 1 + .../providers/manage/members.component.ts | 1 + .../app/admin-console/providers/providers.component.ts | 1 + .../providers/settings/account.component.ts | 1 + .../providers/setup/setup-provider.component.ts | 1 + .../admin-console/providers/setup/setup.component.ts | 1 + .../verify-recover-delete-provider.component.ts | 1 + bitwarden_license/bit-web/src/app/app.component.ts | 1 + .../bit-web/src/app/auth/sso/sso.component.ts | 1 + .../policies/free-families-sponsorship.component.ts | 1 + .../provider-billing-history.component.ts | 1 + .../add-existing-organization-dialog.component.ts | 1 + .../clients/create-client-dialog.component.ts | 1 + .../clients/manage-client-name-dialog.component.ts | 1 + .../manage-client-subscription-dialog.component.ts | 1 + .../providers/setup/setup-business-unit.component.ts | 1 + .../provider-subscription-status.component.ts | 1 + .../subscription/provider-subscription.component.ts | 1 + .../integrations/integrations.component.spec.ts | 2 ++ .../integrations/integrations.component.ts | 1 + .../src/app/secrets-manager/layout/layout.component.ts | 1 + .../app/secrets-manager/layout/navigation.component.ts | 1 + .../app/secrets-manager/overview/overview.component.ts | 1 + .../app/secrets-manager/overview/section.component.ts | 1 + .../projects/dialog/project-delete-dialog.component.ts | 1 + .../projects/dialog/project-dialog.component.ts | 1 + .../projects/guards/project-access.guard.spec.ts | 2 ++ .../projects/project/project-people.component.ts | 1 + .../projects/project/project-secrets.component.ts | 1 + .../project/project-service-accounts.component.ts | 1 + .../projects/project/project.component.ts | 1 + .../projects/projects/projects.component.ts | 1 + .../secrets/dialog/secret-delete.component.ts | 1 + .../secrets/dialog/secret-dialog.component.ts | 1 + .../secrets/dialog/secret-view-dialog.component.ts | 1 + .../app/secrets-manager/secrets/secrets.component.ts | 1 + .../service-accounts/access/access-list.component.ts | 1 + .../service-accounts/access/access-tokens.component.ts | 1 + .../dialogs/access-token-create-dialog.component.ts | 1 + .../access/dialogs/access-token-dialog.component.ts | 1 + .../access/dialogs/expiration-options.component.ts | 1 + .../service-accounts/config/config.component.ts | 1 + .../dialog/service-account-delete-dialog.component.ts | 1 + .../dialog/service-account-dialog.component.ts | 1 + .../event-logs/service-accounts-events.component.ts | 1 + .../guards/service-account-access.guard.spec.ts | 2 ++ .../people/service-account-people.component.ts | 1 + .../projects/service-account-projects.component.ts | 1 + .../service-accounts/service-account.component.ts | 1 + .../service-accounts-list.component.ts | 1 + .../service-accounts/service-accounts.component.ts | 1 + .../dialog/sm-import-error-dialog.component.ts | 1 + .../settings/porting/sm-export.component.ts | 1 + .../settings/porting/sm-import.component.ts | 1 + .../access-policy-selector.component.ts | 1 + .../dialogs/bulk-confirmation-dialog.component.ts | 1 + .../shared/dialogs/bulk-status-dialog.component.ts | 1 + .../app/secrets-manager/shared/new-menu.component.ts | 1 + .../secrets-manager/shared/org-suspended.component.ts | 1 + .../secrets-manager/shared/projects-list.component.ts | 1 + .../secrets-manager/shared/secrets-list.component.ts | 1 + .../trash/dialog/secret-hard-delete.component.ts | 1 + .../trash/dialog/secret-restore.component.ts | 1 + .../src/app/secrets-manager/trash/trash.component.ts | 1 + .../auth/components/environment-selector.component.ts | 1 + .../src/auth/components/two-factor-icon.component.ts | 1 + .../src/auth/components/user-verification.component.ts | 1 + .../add-account-credit-dialog.component.ts | 1 + .../billing/components/invoices/invoices.component.ts | 1 + .../components/invoices/no-invoices.component.ts | 1 + .../manage-tax-information.component.ts | 1 + .../src/billing/directives/not-premium.directive.ts | 1 + libs/angular/src/components/callout.component.ts | 1 + .../src/components/modal/dynamic-modal.component.ts | 1 + libs/angular/src/directives/a11y-invalid.directive.ts | 1 + libs/angular/src/directives/api-action.directive.ts | 1 + libs/angular/src/directives/box-row.directive.ts | 1 + libs/angular/src/directives/copy-text.directive.ts | 1 + libs/angular/src/directives/fallback-src.directive.ts | 1 + libs/angular/src/directives/if-feature.directive.ts | 1 + .../src/directives/input-strip-spaces.directive.ts | 1 + .../angular/src/directives/input-verbatim.directive.ts | 1 + libs/angular/src/directives/launch-click.directive.ts | 1 + libs/angular/src/directives/stop-click.directive.ts | 1 + libs/angular/src/directives/stop-prop.directive.ts | 1 + .../src/directives/true-false-value.directive.ts | 1 + libs/angular/src/pipes/color-password-count.pipe.ts | 5 ++++- libs/angular/src/pipes/color-password.pipe.ts | 5 ++++- libs/angular/src/pipes/credit-card-number.pipe.ts | 5 ++++- libs/angular/src/pipes/search-ciphers.pipe.ts | 1 + libs/angular/src/pipes/search.pipe.ts | 1 + libs/angular/src/pipes/user-name.pipe.ts | 1 + libs/angular/src/pipes/user-type.pipe.ts | 1 + libs/angular/src/platform/pipes/ellipsis.pipe.ts | 1 + libs/angular/src/platform/pipes/fingerprint.pipe.ts | 1 + libs/angular/src/platform/pipes/i18n.pipe.ts | 1 + .../password-strength/password-strength.component.ts | 1 + libs/angular/src/vault/components/icon.component.ts | 1 + libs/components/src/app/app.component.ts | 1 + libs/components/src/input/autofocus.directive.ts | 1 + .../components/src/catchall-settings.component.ts | 1 + .../components/src/credential-generator.component.ts | 1 + .../components/src/forwarder-settings.component.ts | 1 + .../components/src/passphrase-settings.component.ts | 1 + .../components/src/password-generator.component.ts | 1 + .../components/src/password-settings.component.ts | 1 + .../components/src/subaddress-settings.component.ts | 1 + .../components/src/username-generator.component.ts | 1 + .../components/src/username-settings.component.ts | 1 + 311 files changed, 350 insertions(+), 8 deletions(-) diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts index a0990485d49..78ca577a69d 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts @@ -168,18 +168,21 @@ type Story = StoryObj; @Component({ selector: "bit-default-primary-outlet-example-component", template: "

Primary Outlet Example:
your primary component goes here

", + standalone: false, }) class DefaultPrimaryOutletExampleComponent {} @Component({ selector: "bit-default-secondary-outlet-example-component", template: "

Secondary Outlet Example:
your secondary component goes here

", + standalone: false, }) class DefaultSecondaryOutletExampleComponent {} @Component({ selector: "bit-default-env-selector-outlet-example-component", template: "

Env Selector Outlet Example:
your env selector component goes here

", + standalone: false, }) class DefaultEnvSelectorOutletExampleComponent {} @@ -264,6 +267,7 @@ const changedData: ExtensionAnonLayoutWrapperData = { template: ` `, + standalone: false, }) export class DynamicContentExampleComponent { initialData = true; diff --git a/apps/browser/src/auth/popup/set-password.component.ts b/apps/browser/src/auth/popup/set-password.component.ts index accde2e9a09..2a796854531 100644 --- a/apps/browser/src/auth/popup/set-password.component.ts +++ b/apps/browser/src/auth/popup/set-password.component.ts @@ -5,5 +5,6 @@ import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/ang @Component({ selector: "app-set-password", templateUrl: "set-password.component.html", + standalone: false, }) export class SetPasswordComponent extends BaseSetPasswordComponent {} diff --git a/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts b/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts index c56e6578a0b..25a4d01333d 100644 --- a/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts +++ b/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts @@ -18,5 +18,6 @@ import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "@b useExisting: VaultTimeoutInputComponent, }, ], + standalone: false, }) export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {} diff --git a/apps/browser/src/auth/popup/update-temp-password.component.ts b/apps/browser/src/auth/popup/update-temp-password.component.ts index 465bc3f7038..e8cf64b7548 100644 --- a/apps/browser/src/auth/popup/update-temp-password.component.ts +++ b/apps/browser/src/auth/popup/update-temp-password.component.ts @@ -8,6 +8,7 @@ import { postLogoutMessageListener$ } from "./utils/post-logout-message-listener @Component({ selector: "app-update-temp-password", templateUrl: "update-temp-password.component.html", + standalone: false, }) export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent { onSuccessfulChangePassword: () => Promise = this.doOnSuccessfulChangePassword.bind(this); diff --git a/apps/browser/src/key-management/key-connector/remove-password.component.ts b/apps/browser/src/key-management/key-connector/remove-password.component.ts index 3ca9d3a5669..1b07f04ba8a 100644 --- a/apps/browser/src/key-management/key-connector/remove-password.component.ts +++ b/apps/browser/src/key-management/key-connector/remove-password.component.ts @@ -5,5 +5,6 @@ import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitward @Component({ selector: "app-remove-password", templateUrl: "remove-password.component.html", + standalone: false, }) export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts b/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts index 465a6e6c69c..22fb7bf99b9 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts @@ -13,7 +13,10 @@ import { PopupRouterCacheService, popupRouterCacheGuard } from "./popup-router-c const flushPromises = async () => await new Promise(process.nextTick); -@Component({ template: "" }) +@Component({ + template: "", + standalone: false, +}) export class EmptyComponent {} describe("Popup router cache guard", () => { diff --git a/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts b/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts index 2ec75791d1b..60baf94eeae 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts @@ -19,10 +19,16 @@ import { import { PopupViewCacheService } from "./popup-view-cache.service"; -@Component({ template: "" }) +@Component({ + template: "", + standalone: false, +}) export class EmptyComponent {} -@Component({ template: "" }) +@Component({ + template: "", + standalone: false, +}) export class TestComponent { private viewCacheService = inject(PopupViewCacheService); diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index a480e1d6ba3..5f7fbc1fad7 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -43,6 +43,7 @@ import { DesktopSyncVerificationDialogComponent } from "./components/desktop-syn `, + standalone: false, }) export class AppComponent implements OnInit, OnDestroy { private compactModeService = inject(PopupCompactModeService); diff --git a/apps/browser/src/popup/components/user-verification.component.ts b/apps/browser/src/popup/components/user-verification.component.ts index 6befc8973b0..f6cb6cdff12 100644 --- a/apps/browser/src/popup/components/user-verification.component.ts +++ b/apps/browser/src/popup/components/user-verification.component.ts @@ -22,5 +22,6 @@ import { UserVerificationComponent as BaseComponent } from "@bitwarden/angular/a transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]), ]), ], + standalone: false, }) export class UserVerificationComponent extends BaseComponent {} diff --git a/apps/browser/src/popup/tabs-v2.component.ts b/apps/browser/src/popup/tabs-v2.component.ts index 63b539fddce..5df8fb85d6d 100644 --- a/apps/browser/src/popup/tabs-v2.component.ts +++ b/apps/browser/src/popup/tabs-v2.component.ts @@ -13,6 +13,7 @@ import { NavButton } from "../platform/popup/layout/popup-tab-navigation.compone @Component({ selector: "app-tabs-v2", templateUrl: "./tabs-v2.component.html", + standalone: false, }) export class TabsV2Component { private hasActiveBadges$ = this.accountService.activeAccount$ diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 2639d819854..83c982fbaba 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -54,6 +54,7 @@ import { NativeMessagingManifestService } from "../services/native-messaging-man @Component({ selector: "app-settings", templateUrl: "settings.component.html", + standalone: false, }) export class SettingsComponent implements OnInit, OnDestroy { // For use in template diff --git a/apps/desktop/src/app/accounts/vault-timeout-input.component.ts b/apps/desktop/src/app/accounts/vault-timeout-input.component.ts index c56e6578a0b..25a4d01333d 100644 --- a/apps/desktop/src/app/accounts/vault-timeout-input.component.ts +++ b/apps/desktop/src/app/accounts/vault-timeout-input.component.ts @@ -18,5 +18,6 @@ import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "@b useExisting: VaultTimeoutInputComponent, }, ], + standalone: false, }) export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {} diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index c3cfdf49c2c..38c5ca3a2a8 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -93,6 +93,7 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours `, + standalone: false, }) export class AppComponent implements OnInit, OnDestroy { @ViewChild("settings", { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef; diff --git a/apps/desktop/src/app/components/avatar.component.ts b/apps/desktop/src/app/components/avatar.component.ts index d2660763667..1fba864686c 100644 --- a/apps/desktop/src/app/components/avatar.component.ts +++ b/apps/desktop/src/app/components/avatar.component.ts @@ -8,6 +8,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Component({ selector: "app-avatar", template: ``, + standalone: false, }) export class AvatarComponent implements OnChanges, OnInit { @Input() size = 45; diff --git a/apps/desktop/src/app/layout/account-switcher.component.ts b/apps/desktop/src/app/layout/account-switcher.component.ts index d8ffa5ae546..a54674c3a1e 100644 --- a/apps/desktop/src/app/layout/account-switcher.component.ts +++ b/apps/desktop/src/app/layout/account-switcher.component.ts @@ -54,6 +54,7 @@ type InactiveAccount = ActiveAccount & { transition("* => void", animate("100ms linear", style({ opacity: 0 }))), ]), ], + standalone: false, }) export class AccountSwitcherComponent implements OnInit { activeAccount$: Observable; diff --git a/apps/desktop/src/app/layout/header.component.ts b/apps/desktop/src/app/layout/header.component.ts index 1cf697ad4ed..9aef093423f 100644 --- a/apps/desktop/src/app/layout/header.component.ts +++ b/apps/desktop/src/app/layout/header.component.ts @@ -3,5 +3,6 @@ import { Component } from "@angular/core"; @Component({ selector: "app-header", templateUrl: "header.component.html", + standalone: false, }) export class HeaderComponent {} diff --git a/apps/desktop/src/app/layout/search/search.component.ts b/apps/desktop/src/app/layout/search/search.component.ts index a9faf2a2414..70196d74dda 100644 --- a/apps/desktop/src/app/layout/search/search.component.ts +++ b/apps/desktop/src/app/layout/search/search.component.ts @@ -11,6 +11,7 @@ import { SearchBarService, SearchBarState } from "./search-bar.service"; @Component({ selector: "app-search", templateUrl: "search.component.html", + standalone: false, }) export class SearchComponent implements OnInit, OnDestroy { state: SearchBarState; diff --git a/apps/desktop/src/auth/set-password.component.ts b/apps/desktop/src/auth/set-password.component.ts index 5a78fb08c47..48b18d7294c 100644 --- a/apps/desktop/src/auth/set-password.component.ts +++ b/apps/desktop/src/auth/set-password.component.ts @@ -28,6 +28,7 @@ const BroadcasterSubscriptionId = "SetPasswordComponent"; @Component({ selector: "app-set-password", templateUrl: "set-password.component.html", + standalone: false, }) export class SetPasswordComponent extends BaseSetPasswordComponent implements OnInit, OnDestroy { constructor( diff --git a/apps/desktop/src/auth/update-temp-password.component.ts b/apps/desktop/src/auth/update-temp-password.component.ts index f7d952b97f4..ead10660b92 100644 --- a/apps/desktop/src/auth/update-temp-password.component.ts +++ b/apps/desktop/src/auth/update-temp-password.component.ts @@ -5,5 +5,6 @@ import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from " @Component({ selector: "app-update-temp-password", templateUrl: "update-temp-password.component.html", + standalone: false, }) export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {} diff --git a/apps/desktop/src/billing/app/accounts/premium.component.ts b/apps/desktop/src/billing/app/accounts/premium.component.ts index 1b4573fe6d6..5d0fa7a5dde 100644 --- a/apps/desktop/src/billing/app/accounts/premium.component.ts +++ b/apps/desktop/src/billing/app/accounts/premium.component.ts @@ -13,6 +13,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "app-premium", templateUrl: "premium.component.html", + standalone: false, }) export class PremiumComponent extends BasePremiumComponent { constructor( diff --git a/apps/desktop/src/key-management/key-connector/remove-password.component.ts b/apps/desktop/src/key-management/key-connector/remove-password.component.ts index 3ca9d3a5669..1b07f04ba8a 100644 --- a/apps/desktop/src/key-management/key-connector/remove-password.component.ts +++ b/apps/desktop/src/key-management/key-connector/remove-password.component.ts @@ -5,5 +5,6 @@ import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitward @Component({ selector: "app-remove-password", templateUrl: "remove-password.component.html", + standalone: false, }) export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts b/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts index 6992455a8a6..b4be2406c4b 100644 --- a/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts @@ -7,6 +7,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic @Component({ selector: "app-vault-add-edit-custom-fields", templateUrl: "add-edit-custom-fields.component.html", + standalone: false, }) export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent { constructor(i18nService: I18nService, eventCollectionService: EventCollectionService) { diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.ts b/apps/desktop/src/vault/app/vault/add-edit.component.ts index 2c8b5a8321a..eb04003a418 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit.component.ts @@ -30,6 +30,7 @@ const BroadcasterSubscriptionId = "AddEditComponent"; @Component({ selector: "app-vault-add-edit", templateUrl: "add-edit.component.html", + standalone: false, }) export class AddEditComponent extends BaseAddEditComponent implements OnInit, OnChanges, OnDestroy { @ViewChild("form") diff --git a/apps/desktop/src/vault/app/vault/attachments.component.ts b/apps/desktop/src/vault/app/vault/attachments.component.ts index a2cea5f2722..a116a4d2acb 100644 --- a/apps/desktop/src/vault/app/vault/attachments.component.ts +++ b/apps/desktop/src/vault/app/vault/attachments.component.ts @@ -18,6 +18,7 @@ import { KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-vault-attachments", templateUrl: "attachments.component.html", + standalone: false, }) export class AttachmentsComponent extends BaseAttachmentsComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/collections.component.ts b/apps/desktop/src/vault/app/vault/collections.component.ts index e7684c3c07a..46455d04cd2 100644 --- a/apps/desktop/src/vault/app/vault/collections.component.ts +++ b/apps/desktop/src/vault/app/vault/collections.component.ts @@ -13,6 +13,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-vault-collections", templateUrl: "collections.component.html", + standalone: false, }) export class CollectionsComponent extends BaseCollectionsComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts b/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts index cdb879693c0..cecd5cd671c 100644 --- a/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts @@ -14,6 +14,7 @@ import { KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-folder-add-edit", templateUrl: "folder-add-edit.component.html", + standalone: false, }) export class FolderAddEditComponent extends BaseFolderAddEditComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/password-history.component.ts b/apps/desktop/src/vault/app/vault/password-history.component.ts index 4a87617d8f4..e83ce0d56ea 100644 --- a/apps/desktop/src/vault/app/vault/password-history.component.ts +++ b/apps/desktop/src/vault/app/vault/password-history.component.ts @@ -10,6 +10,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-password-history", templateUrl: "password-history.component.html", + standalone: false, }) export class PasswordHistoryComponent extends BasePasswordHistoryComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/share.component.ts b/apps/desktop/src/vault/app/vault/share.component.ts index 6926e7e2abf..50842439323 100644 --- a/apps/desktop/src/vault/app/vault/share.component.ts +++ b/apps/desktop/src/vault/app/vault/share.component.ts @@ -13,6 +13,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi @Component({ selector: "app-vault-share", templateUrl: "share.component.html", + standalone: false, }) export class ShareComponent extends BaseShareComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts index 161c9ae5353..22372410e5b 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts @@ -5,5 +5,6 @@ import { CollectionFilterComponent as BaseCollectionFilterComponent } from "@bit @Component({ selector: "app-collection-filter", templateUrl: "collection-filter.component.html", + standalone: false, }) export class CollectionFilterComponent extends BaseCollectionFilterComponent {} diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts index 790d31a65e6..d7364808f6d 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts @@ -5,5 +5,6 @@ import { FolderFilterComponent as BaseFolderFilterComponent } from "@bitwarden/a @Component({ selector: "app-folder-filter", templateUrl: "folder-filter.component.html", + standalone: false, }) export class FolderFilterComponent extends BaseFolderFilterComponent {} diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts index 39f1c0200ea..33a47cdc91f 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts @@ -12,6 +12,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-organization-filter", templateUrl: "organization-filter.component.html", + standalone: false, }) export class OrganizationFilterComponent extends BaseOrganizationFilterComponent { get show() { diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts index 5d43fd52d20..276b11d7138 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts @@ -5,5 +5,6 @@ import { StatusFilterComponent as BaseStatusFilterComponent } from "@bitwarden/a @Component({ selector: "app-status-filter", templateUrl: "status-filter.component.html", + standalone: false, }) export class StatusFilterComponent extends BaseStatusFilterComponent {} diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts index 5727cc0e9d5..5920233b206 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts @@ -5,6 +5,7 @@ import { TypeFilterComponent as BaseTypeFilterComponent } from "@bitwarden/angul @Component({ selector: "app-type-filter", templateUrl: "type-filter.component.html", + standalone: false, }) export class TypeFilterComponent extends BaseTypeFilterComponent { constructor() { diff --git a/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts index 12ac1fef425..161d22687e8 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts @@ -5,5 +5,6 @@ import { VaultFilterComponent as BaseVaultFilterComponent } from "@bitwarden/ang @Component({ selector: "app-vault-filter", templateUrl: "vault-filter.component.html", + standalone: false, }) export class VaultFilterComponent extends BaseVaultFilterComponent {} diff --git a/apps/desktop/src/vault/app/vault/vault-items.component.ts b/apps/desktop/src/vault/app/vault/vault-items.component.ts index d5838459ff7..2d1ba784753 100644 --- a/apps/desktop/src/vault/app/vault/vault-items.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-items.component.ts @@ -14,6 +14,7 @@ import { SearchBarService } from "../../../app/layout/search/search-bar.service" @Component({ selector: "app-vault-items", templateUrl: "vault-items.component.html", + standalone: false, }) export class VaultItemsComponent extends BaseVaultItemsComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts index 6c0d5ef81d0..560855347b3 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.ts +++ b/apps/desktop/src/vault/app/vault/vault.component.ts @@ -57,6 +57,7 @@ const BroadcasterSubscriptionId = "VaultComponent"; @Component({ selector: "app-vault", templateUrl: "vault.component.html", + standalone: false, }) export class VaultComponent implements OnInit, OnDestroy { @ViewChild(ViewComponent) viewComponent: ViewComponent; diff --git a/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts b/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts index 249f83c4444..efe61ad1fa7 100644 --- a/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts +++ b/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts @@ -6,6 +6,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve @Component({ selector: "app-vault-view-custom-fields", templateUrl: "view-custom-fields.component.html", + standalone: false, }) export class ViewCustomFieldsComponent extends BaseViewCustomFieldsComponent { constructor(eventCollectionService: EventCollectionService) { diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index e74b07445da..084a9a747ed 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -42,6 +42,7 @@ const BroadcasterSubscriptionId = "ViewComponent"; @Component({ selector: "app-vault-view", templateUrl: "view.component.html", + standalone: false, }) export class ViewComponent extends BaseViewComponent implements OnInit, OnDestroy, OnChanges { @Output() onViewCipherPasswordHistory = new EventEmitter(); diff --git a/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts index 8e5f261bc26..8f703acf9af 100644 --- a/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts @@ -10,6 +10,7 @@ import { GroupView } from "../../core"; @Component({ selector: "app-group-badge", templateUrl: "group-name-badge.component.html", + standalone: false, }) export class GroupNameBadgeComponent implements OnChanges { @Input() selectedGroups: SelectionReadOnlyRequest[]; diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts index 13e90d5275c..f7d7acfdc2d 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts @@ -26,6 +26,7 @@ import { CollectionFilter } from "../../../../vault/individual-vault/vault-filte selector: "app-organization-vault-filter", templateUrl: "../../../../vault/individual-vault/vault-filter/components/vault-filter.component.html", + standalone: false, }) export class VaultFilterComponent extends BaseVaultFilterComponent diff --git a/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts b/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts index fc168f842dc..cd14b73a156 100644 --- a/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts +++ b/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts @@ -9,6 +9,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv @Component({ selector: "app-org-info", templateUrl: "organization-information.component.html", + standalone: false, }) export class OrganizationInformationComponent implements OnInit { @Input() nameOnly = false; diff --git a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts index f5fce0e5e42..bc4a942301a 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts @@ -21,16 +21,19 @@ import { isEnterpriseOrgGuard } from "./is-enterprise-org.guard"; @Component({ template: "

This is the home screen!

", + standalone: false, }) export class HomescreenComponent {} @Component({ template: "

This component can only be accessed by a enterprise organization!

", + standalone: false, }) export class IsEnterpriseOrganizationComponent {} @Component({ template: "

This is the organization upgrade screen!

", + standalone: false, }) export class OrganizationUpgradeScreenComponent {} diff --git a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts index 8efed8cefa2..ab5fd79321a 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts @@ -20,16 +20,19 @@ import { isPaidOrgGuard } from "./is-paid-org.guard"; @Component({ template: "

This is the home screen!

", + standalone: false, }) export class HomescreenComponent {} @Component({ template: "

This component can only be accessed by a paid organization!

", + standalone: false, }) export class PaidOrganizationOnlyComponent {} @Component({ template: "

This is the organization upgrade screen!

", + standalone: false, }) export class OrganizationUpgradeScreenComponent {} diff --git a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts index fa348867a86..9dc084484f3 100644 --- a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts @@ -19,16 +19,19 @@ import { organizationRedirectGuard } from "./org-redirect.guard"; @Component({ template: "

This is the home screen!

", + standalone: false, }) export class HomescreenComponent {} @Component({ template: "

This is the admin console!

", + standalone: false, }) export class AdminConsoleComponent {} @Component({ template: "

This is a subroute of the admin console!

", + standalone: false, }) export class AdminConsoleSubrouteComponent {} diff --git a/apps/web/src/app/admin-console/organizations/manage/events.component.ts b/apps/web/src/app/admin-console/organizations/manage/events.component.ts index 737a38ee2ab..3daa6c17d07 100644 --- a/apps/web/src/app/admin-console/organizations/manage/events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/events.component.ts @@ -48,6 +48,7 @@ const EVENT_SYSTEM_USER_TO_TRANSLATION: Record = { @Component({ selector: "app-org-events", templateUrl: "events.component.html", + standalone: false, }) export class EventsComponent extends BaseEventsComponent implements OnInit, OnDestroy { exportFileName = "org-events"; diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts index f29b4b642cb..53a6a3cf196 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts @@ -110,6 +110,7 @@ export const openGroupAddEditDialog = ( @Component({ selector: "app-group-add-edit", templateUrl: "group-add-edit.component.html", + standalone: false, }) export class GroupAddEditComponent implements OnInit, OnDestroy { private organization$ = this.accountService.activeAccount$.pipe( diff --git a/apps/web/src/app/admin-console/organizations/manage/groups.component.ts b/apps/web/src/app/admin-console/organizations/manage/groups.component.ts index 669dcc251ca..6459cd1f857 100644 --- a/apps/web/src/app/admin-console/organizations/manage/groups.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/groups.component.ts @@ -75,6 +75,7 @@ const groupsFilter = (filter: string) => { @Component({ templateUrl: "groups.component.html", + standalone: false, }) export class GroupsComponent { loading = true; diff --git a/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts b/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts index b5068ba55a6..03b77cfaa71 100644 --- a/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts @@ -18,6 +18,7 @@ export type UserConfirmDialogData = { @Component({ selector: "app-user-confirm", templateUrl: "user-confirm.component.html", + standalone: false, }) export class UserConfirmComponent implements OnInit { name: string; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts index c19984f980d..4ec50799ae0 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts @@ -33,6 +33,7 @@ type BulkConfirmDialogParams = { @Component({ templateUrl: "bulk-confirm-dialog.component.html", + standalone: false, }) export class BulkConfirmDialogComponent extends BaseBulkConfirmComponent { organizationId: string; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts index 51ba98fabb9..8fb60e85b08 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts @@ -18,6 +18,7 @@ type BulkDeleteDialogParams = { @Component({ templateUrl: "bulk-delete-dialog.component.html", + standalone: false, }) export class BulkDeleteDialogComponent { organizationId: string; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts index e01809789f3..9132625c587 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts @@ -22,6 +22,7 @@ export type BulkEnableSecretsManagerDialogData = { @Component({ templateUrl: `bulk-enable-sm-dialog.component.html`, + standalone: false, }) export class BulkEnableSecretsManagerDialogComponent implements OnInit { protected dataSource = new TableDataSource(); diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts index 00711e355cb..5bbc6f093f0 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts @@ -21,6 +21,7 @@ type BulkRemoveDialogParams = { @Component({ templateUrl: "bulk-remove-dialog.component.html", + standalone: false, }) export class BulkRemoveDialogComponent extends BaseBulkRemoveComponent { organizationId: string; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts index 5cb2c2e3d4e..ac99a9b51de 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts @@ -18,6 +18,7 @@ type BulkRestoreDialogParams = { @Component({ selector: "app-bulk-restore-revoke", templateUrl: "bulk-restore-revoke.component.html", + standalone: false, }) export class BulkRestoreRevokeComponent { isRevoking: boolean; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts index b8a2f45053b..078ba6c1fd1 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts @@ -41,6 +41,7 @@ type BulkStatusDialogData = { @Component({ selector: "app-bulk-status", templateUrl: "bulk-status.component.html", + standalone: false, }) export class BulkStatusComponent implements OnInit { users: BulkStatusEntry[]; diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index 8349b44c735..10bbc5cfe52 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -106,6 +106,7 @@ export enum MemberDialogResult { @Component({ templateUrl: "member-dialog.component.html", + standalone: false, }) export class MemberDialogComponent implements OnDestroy { loading = true; diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/nested-checkbox.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/nested-checkbox.component.ts index 648a5a6ff26..9a2025c2b30 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/nested-checkbox.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/nested-checkbox.component.ts @@ -10,6 +10,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Component({ selector: "app-nested-checkbox", templateUrl: "nested-checkbox.component.html", + standalone: false, }) export class NestedCheckboxComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts index 4e78d4dc91f..80f0745f6d5 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts @@ -59,6 +59,7 @@ export enum ResetPasswordDialogResult { @Component({ selector: "app-reset-password", templateUrl: "reset-password.component.html", + standalone: false, }) /** * Used in a dialog for initiating the account recovery process against a diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index 6a3ca58b73d..834aa2c7111 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -88,6 +88,7 @@ class MembersTableDataSource extends PeopleTableDataSource @Component({ templateUrl: "members.component.html", + standalone: false, }) export class MembersComponent extends BaseMembersComponent { userType = OrganizationUserType; diff --git a/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts b/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts index 2acf175e3da..b323ac00d34 100644 --- a/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts @@ -14,5 +14,6 @@ export class DisableSendPolicy extends BasePolicy { @Component({ selector: "policy-disable-send", templateUrl: "disable-send.component.html", + standalone: false, }) export class DisableSendPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts b/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts index 328989df66b..54cf1be88fc 100644 --- a/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts @@ -28,6 +28,7 @@ export class MasterPasswordPolicy extends BasePolicy { @Component({ selector: "policy-master-password", templateUrl: "master-password.component.html", + standalone: false, }) export class MasterPasswordPolicyComponent extends BasePolicyComponent implements OnInit { MinPasswordLength = Utils.minimumPasswordLength; diff --git a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts index 4439f974e55..f11b14aea38 100644 --- a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts @@ -21,6 +21,7 @@ export class PasswordGeneratorPolicy extends BasePolicy { @Component({ selector: "policy-password-generator", templateUrl: "password-generator.component.html", + standalone: false, }) export class PasswordGeneratorPolicyComponent extends BasePolicyComponent { // these properties forward the application default settings to the UI diff --git a/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts b/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts index 36c79a61fe4..ef92ee90581 100644 --- a/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts @@ -14,5 +14,6 @@ export class PersonalOwnershipPolicy extends BasePolicy { @Component({ selector: "policy-personal-ownership", templateUrl: "personal-ownership.component.html", + standalone: false, }) export class PersonalOwnershipPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts index 6e3b34eaa30..73f0d99b4f9 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts @@ -31,6 +31,7 @@ import { PolicyEditComponent, PolicyEditDialogResult } from "./policy-edit.compo @Component({ selector: "app-org-policies", templateUrl: "policies.component.html", + standalone: false, }) export class PoliciesComponent implements OnInit { loading = true; diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts index 4d722840e23..d3d03d2aaae 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts @@ -50,6 +50,7 @@ export enum PolicyEditDialogResult { @Component({ selector: "app-policy-edit", templateUrl: "policy-edit.component.html", + standalone: false, }) export class PolicyEditComponent implements AfterViewInit { @ViewChild("policyForm", { read: ViewContainerRef, static: true }) diff --git a/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts index b737c803b5f..0d0f42b603e 100644 --- a/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts @@ -14,5 +14,6 @@ export class RemoveUnlockWithPinPolicy extends BasePolicy { @Component({ selector: "remove-unlock-with-pin", templateUrl: "remove-unlock-with-pin.component.html", + standalone: false, }) export class RemoveUnlockWithPinPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts b/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts index ea85168f986..21de143dea6 100644 --- a/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts @@ -19,5 +19,6 @@ export class RequireSsoPolicy extends BasePolicy { @Component({ selector: "policy-require-sso", templateUrl: "require-sso.component.html", + standalone: false, }) export class RequireSsoPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts b/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts index 80e4e5254ff..62fc42f6a06 100644 --- a/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts @@ -27,6 +27,7 @@ export class ResetPasswordPolicy extends BasePolicy { @Component({ selector: "policy-reset-password", templateUrl: "reset-password.component.html", + standalone: false, }) export class ResetPasswordPolicyComponent extends BasePolicyComponent implements OnInit { data = this.formBuilder.group({ diff --git a/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts b/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts index af6d1f38694..9a0a8871296 100644 --- a/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts @@ -15,6 +15,7 @@ export class SendOptionsPolicy extends BasePolicy { @Component({ selector: "policy-send-options", templateUrl: "send-options.component.html", + standalone: false, }) export class SendOptionsPolicyComponent extends BasePolicyComponent { data = this.formBuilder.group({ diff --git a/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts b/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts index a40ec87bf6a..ad32b4218bc 100644 --- a/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts @@ -14,6 +14,7 @@ export class SingleOrgPolicy extends BasePolicy { @Component({ selector: "policy-single-org", templateUrl: "single-org.component.html", + standalone: false, }) export class SingleOrgPolicyComponent extends BasePolicyComponent implements OnInit { async ngOnInit() { diff --git a/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts b/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts index 51151808872..691e12c72f6 100644 --- a/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts @@ -14,5 +14,6 @@ export class TwoFactorAuthenticationPolicy extends BasePolicy { @Component({ selector: "policy-two-factor-authentication", templateUrl: "two-factor-authentication.component.html", + standalone: false, }) export class TwoFactorAuthenticationPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts index 9fd3170b73a..52cb24c90d1 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts +++ b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts @@ -17,6 +17,7 @@ import { ReportVariant, reports, ReportType, ReportEntry } from "../../../dirt/r @Component({ selector: "app-org-reports-home", templateUrl: "reports-home.component.html", + standalone: false, }) export class ReportsHomeComponent implements OnInit { reports$: Observable; diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.ts b/apps/web/src/app/admin-console/organizations/settings/account.component.ts index f3997fe669e..b376c48b39a 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.ts @@ -41,6 +41,7 @@ import { DeleteOrganizationDialogResult, openDeleteOrganizationDialog } from "./ @Component({ selector: "app-org-account", templateUrl: "account.component.html", + standalone: false, }) export class AccountComponent implements OnInit, OnDestroy { selfHosted = false; diff --git a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts index 014b8e3a3ef..d14e912f83e 100644 --- a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts @@ -29,6 +29,7 @@ import { TwoFactorVerifyComponent } from "../../../auth/settings/two-factor/two- @Component({ selector: "app-two-factor-setup", templateUrl: "../../../auth/settings/two-factor/two-factor-setup.component.html", + standalone: false, }) export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent implements OnInit { tabbedHeader = false; diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts index 1db1fc8a06e..366df34b2b8 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts @@ -55,6 +55,7 @@ export enum PermissionMode { multi: true, }, ], + standalone: false, }) export class AccessSelectorComponent implements ControlValueAccessor, OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts index 3d43c63efb0..673d09ec0f0 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts @@ -5,6 +5,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic @Pipe({ name: "userType", + standalone: false, }) export class UserTypePipe implements PipeTransform { constructor(private i18nService: I18nService) {} diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts b/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts index 7ceaed28f80..4df6defe8ad 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts +++ b/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts @@ -19,6 +19,7 @@ import { BaseAcceptComponent } from "../../../common/base.accept.component"; @Component({ selector: "app-accept-family-sponsorship", templateUrl: "accept-family-sponsorship.component.html", + standalone: false, }) export class AcceptFamilySponsorshipComponent extends BaseAcceptComponent { protected logo = BitwardenLogo; diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index b94ce004313..cac0487d05d 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -49,6 +49,7 @@ const IdleTimeout = 60000 * 10; // 10 minutes @Component({ selector: "app-root", templateUrl: "app.component.html", + standalone: false, }) export class AppComponent implements OnDestroy, OnInit { private lastActivity: Date = null; diff --git a/apps/web/src/app/auth/guards/deep-link.guard.spec.ts b/apps/web/src/app/auth/guards/deep-link.guard.spec.ts index f9ced556e47..82ed004cf54 100644 --- a/apps/web/src/app/auth/guards/deep-link.guard.spec.ts +++ b/apps/web/src/app/auth/guards/deep-link.guard.spec.ts @@ -13,16 +13,19 @@ import { deepLinkGuard } from "./deep-link.guard"; @Component({ template: "", + standalone: false, }) export class GuardedRouteTestComponent {} @Component({ template: "", + standalone: false, }) export class LockTestComponent {} @Component({ template: "", + standalone: false, }) export class RedirectTestComponent {} diff --git a/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts b/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts index 8f3a5ca3c37..d4a381159ab 100644 --- a/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts +++ b/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts @@ -7,6 +7,7 @@ import { CreatePasskeyIcon } from "@bitwarden/angular/auth/icons/create-passkey. @Component({ selector: "app-login-via-webauthn", templateUrl: "login-via-webauthn.component.html", + standalone: false, }) export class LoginViaWebAuthnComponent extends BaseLoginViaWebAuthnComponent { protected readonly Icons = { CreatePasskeyIcon, CreatePasskeyFailedIcon }; diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts index 197b4031998..838a3029711 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts @@ -14,6 +14,7 @@ import { OrganizationInvite } from "./organization-invite"; @Component({ templateUrl: "accept-organization.component.html", + standalone: false, }) export class AcceptOrganizationComponent extends BaseAcceptComponent { orgName$ = this.acceptOrganizationInviteService.orgName$; diff --git a/apps/web/src/app/auth/recover-delete.component.ts b/apps/web/src/app/auth/recover-delete.component.ts index 6b45421911d..7381d526879 100644 --- a/apps/web/src/app/auth/recover-delete.component.ts +++ b/apps/web/src/app/auth/recover-delete.component.ts @@ -13,6 +13,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-recover-delete", templateUrl: "recover-delete.component.html", + standalone: false, }) export class RecoverDeleteComponent { protected recoverDeleteForm = new FormGroup({ diff --git a/apps/web/src/app/auth/recover-two-factor.component.ts b/apps/web/src/app/auth/recover-two-factor.component.ts index 106cd32a945..a69da0a66bf 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.ts +++ b/apps/web/src/app/auth/recover-two-factor.component.ts @@ -16,6 +16,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-recover-two-factor", templateUrl: "recover-two-factor.component.html", + standalone: false, }) export class RecoverTwoFactorComponent implements OnInit { protected formGroup = new FormGroup({ diff --git a/apps/web/src/app/auth/set-password.component.ts b/apps/web/src/app/auth/set-password.component.ts index ccd329dd640..e297426f2c1 100644 --- a/apps/web/src/app/auth/set-password.component.ts +++ b/apps/web/src/app/auth/set-password.component.ts @@ -11,6 +11,7 @@ import { AcceptOrganizationInviteService } from "./organization-invite/accept-or @Component({ selector: "app-set-password", templateUrl: "set-password.component.html", + standalone: false, }) export class SetPasswordComponent extends BaseSetPasswordComponent { routerService = inject(RouterService); diff --git a/apps/web/src/app/auth/settings/account/account.component.ts b/apps/web/src/app/auth/settings/account/account.component.ts index d7ba8058b81..cfc01f17674 100644 --- a/apps/web/src/app/auth/settings/account/account.component.ts +++ b/apps/web/src/app/auth/settings/account/account.component.ts @@ -17,6 +17,7 @@ import { SetAccountVerifyDevicesDialogComponent } from "./set-account-verify-dev @Component({ selector: "app-account", templateUrl: "account.component.html", + standalone: false, }) export class AccountComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts index ba2a34c7143..5d71333c0de 100644 --- a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts @@ -31,6 +31,7 @@ type ChangeAvatarDialogData = { @Component({ templateUrl: "change-avatar-dialog.component.html", encapsulation: ViewEncapsulation.None, + standalone: false, }) export class ChangeAvatarDialogComponent implements OnInit, OnDestroy { profile: ProfileResponse; diff --git a/apps/web/src/app/auth/settings/account/change-email.component.ts b/apps/web/src/app/auth/settings/account/change-email.component.ts index caf7e0933b0..c86c8c2f4f7 100644 --- a/apps/web/src/app/auth/settings/account/change-email.component.ts +++ b/apps/web/src/app/auth/settings/account/change-email.component.ts @@ -17,6 +17,7 @@ import { KdfConfigService, KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-change-email", templateUrl: "change-email.component.html", + standalone: false, }) export class ChangeEmailComponent implements OnInit { tokenSent = false; diff --git a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts index a7c466d4ffc..da4d2dce9d7 100644 --- a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts +++ b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts @@ -12,6 +12,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "app-deauthorize-sessions", templateUrl: "deauthorize-sessions.component.html", + standalone: false, }) export class DeauthorizeSessionsComponent { deauthForm = this.formBuilder.group({ diff --git a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts index 07e6052e0a1..8a3575af5ba 100644 --- a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts @@ -11,6 +11,7 @@ import { DialogRef, DialogService, ToastService } from "@bitwarden/components"; @Component({ templateUrl: "delete-account-dialog.component.html", + standalone: false, }) export class DeleteAccountDialogComponent { deleteForm = this.formBuilder.group({ diff --git a/apps/web/src/app/auth/settings/account/profile.component.ts b/apps/web/src/app/auth/settings/account/profile.component.ts index a87e571643d..dc3997f58bb 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.ts +++ b/apps/web/src/app/auth/settings/account/profile.component.ts @@ -19,6 +19,7 @@ import { ChangeAvatarDialogComponent } from "./change-avatar-dialog.component"; @Component({ selector: "app-profile", templateUrl: "profile.component.html", + standalone: false, }) export class ProfileComponent implements OnInit, OnDestroy { loading = true; diff --git a/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts index 7a746481563..33c307882c5 100644 --- a/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts +++ b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts @@ -24,6 +24,7 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; > `, + standalone: false, }) export class SelectableAvatarComponent { @Input() id: string; diff --git a/apps/web/src/app/auth/settings/change-password.component.ts b/apps/web/src/app/auth/settings/change-password.component.ts index ffa5247ad08..f1ba9281f69 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -32,6 +32,7 @@ import { UserKeyRotationService } from "../../key-management/key-rotation/user-k @Component({ selector: "app-change-password", templateUrl: "change-password.component.html", + standalone: false, }) export class ChangePasswordComponent extends BaseChangePasswordComponent diff --git a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts index 95afc167374..cd7a585f3b1 100644 --- a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts @@ -26,6 +26,7 @@ type EmergencyAccessConfirmDialogData = { @Component({ selector: "emergency-access-confirm", templateUrl: "emergency-access-confirm.component.html", + standalone: false, }) export class EmergencyAccessConfirmComponent implements OnInit { loading = true; diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts index 1a6510ef011..2f3f3a20b04 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts @@ -36,6 +36,7 @@ export enum EmergencyAccessAddEditDialogResult { @Component({ selector: "emergency-access-add-edit", templateUrl: "emergency-access-add-edit.component.html", + standalone: false, }) export class EmergencyAccessAddEditComponent implements OnInit { loading = true; diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts index f55d731d7f2..23bf0c22bc7 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts @@ -42,6 +42,7 @@ import { @Component({ selector: "emergency-access", templateUrl: "emergency-access.component.html", + standalone: false, }) export class EmergencyAccessComponent implements OnInit { loaded = false; diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts index edb85dc0f1a..d683545db59 100644 --- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts @@ -40,6 +40,7 @@ type EmergencyAccessTakeoverDialogData = { @Component({ selector: "emergency-access-takeover", templateUrl: "emergency-access-takeover.component.html", + standalone: false, }) export class EmergencyAccessTakeoverComponent extends ChangePasswordComponent diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts index 55ebf860cff..607e6e6a2c7 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts @@ -15,6 +15,7 @@ import { EmergencyViewDialogComponent } from "./emergency-view-dialog.component" selector: "emergency-access-view", templateUrl: "emergency-access-view.component.html", providers: [{ provide: CipherFormConfigService, useClass: DefaultCipherFormConfigService }], + standalone: false, }) export class EmergencyAccessViewComponent implements OnInit { id: EmergencyAccessId | null = null; diff --git a/apps/web/src/app/auth/settings/security/api-key.component.ts b/apps/web/src/app/auth/settings/security/api-key.component.ts index 26a70476102..4f87c082881 100644 --- a/apps/web/src/app/auth/settings/security/api-key.component.ts +++ b/apps/web/src/app/auth/settings/security/api-key.component.ts @@ -23,6 +23,7 @@ export type ApiKeyDialogData = { @Component({ selector: "app-api-key", templateUrl: "api-key.component.html", + standalone: false, }) export class ApiKeyComponent { clientId: string; diff --git a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts index debe390d878..0bfc46eea96 100644 --- a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts +++ b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts @@ -16,6 +16,7 @@ import { KdfConfig, KdfType, KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-change-kdf-confirmation", templateUrl: "change-kdf-confirmation.component.html", + standalone: false, }) export class ChangeKdfConfirmationComponent { kdfConfig: KdfConfig; diff --git a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts index cbbef0e016b..a059ede77b4 100644 --- a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts +++ b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts @@ -21,6 +21,7 @@ import { ChangeKdfConfirmationComponent } from "./change-kdf-confirmation.compon @Component({ selector: "app-change-kdf", templateUrl: "change-kdf.component.html", + standalone: false, }) export class ChangeKdfComponent implements OnInit, OnDestroy { kdfConfig: KdfConfig = DEFAULT_KDF_CONFIG; diff --git a/apps/web/src/app/auth/settings/security/security-keys.component.ts b/apps/web/src/app/auth/settings/security/security-keys.component.ts index 70e33b242b3..98e743f57dc 100644 --- a/apps/web/src/app/auth/settings/security/security-keys.component.ts +++ b/apps/web/src/app/auth/settings/security/security-keys.component.ts @@ -13,6 +13,7 @@ import { ApiKeyComponent } from "./api-key.component"; @Component({ selector: "app-security-keys", templateUrl: "security-keys.component.html", + standalone: false, }) export class SecurityKeysComponent implements OnInit { showChangeKdf = true; diff --git a/apps/web/src/app/auth/settings/security/security.component.ts b/apps/web/src/app/auth/settings/security/security.component.ts index d643b565df2..4f70a19e378 100644 --- a/apps/web/src/app/auth/settings/security/security.component.ts +++ b/apps/web/src/app/auth/settings/security/security.component.ts @@ -6,6 +6,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co @Component({ selector: "app-security", templateUrl: "security.component.html", + standalone: false, }) export class SecurityComponent implements OnInit { showChangePassword = true; diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts index 8e7e25896ab..22c3b4376c5 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts @@ -33,6 +33,7 @@ type Step = @Component({ templateUrl: "create-credential-dialog.component.html", + standalone: false, }) export class CreateCredentialDialogComponent implements OnInit { protected readonly NameMaxCharacters = 50; diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts index 8f6bf1778e8..ea766a302ca 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts @@ -26,6 +26,7 @@ export interface DeleteCredentialDialogParams { @Component({ templateUrl: "delete-credential-dialog.component.html", + standalone: false, }) export class DeleteCredentialDialogComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.ts index b402e53abf2..dd1ac45a9b6 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.ts @@ -23,6 +23,7 @@ export interface EnableEncryptionDialogParams { @Component({ templateUrl: "enable-encryption-dialog.component.html", + standalone: false, }) export class EnableEncryptionDialogComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts index 13c7993768c..94e926ac138 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts @@ -23,6 +23,7 @@ import { openEnableCredentialDialogComponent } from "./enable-encryption-dialog/ host: { "aria-live": "polite", }, + standalone: false, }) export class WebauthnLoginSettingsComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/auth/shared/components/user-verification/user-verification-prompt.component.ts b/apps/web/src/app/auth/shared/components/user-verification/user-verification-prompt.component.ts index 282bce4c95d..77df374f3ed 100644 --- a/apps/web/src/app/auth/shared/components/user-verification/user-verification-prompt.component.ts +++ b/apps/web/src/app/auth/shared/components/user-verification/user-verification-prompt.component.ts @@ -23,6 +23,7 @@ import { */ @Component({ templateUrl: "user-verification-prompt.component.html", + standalone: false, }) export class UserVerificationPromptComponent extends BaseUserVerificationPrompt { constructor( diff --git a/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.ts b/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.ts index 94d319524f7..42f4b26fb36 100644 --- a/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.ts +++ b/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.ts @@ -23,5 +23,6 @@ import { UserVerificationComponent as BaseComponent } from "@bitwarden/angular/a transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]), ]), ], + standalone: false, }) export class UserVerificationComponent extends BaseComponent {} diff --git a/apps/web/src/app/auth/update-password.component.ts b/apps/web/src/app/auth/update-password.component.ts index da62a6812f1..c975f7c4168 100644 --- a/apps/web/src/app/auth/update-password.component.ts +++ b/apps/web/src/app/auth/update-password.component.ts @@ -9,6 +9,7 @@ import { AcceptOrganizationInviteService } from "./organization-invite/accept-or @Component({ selector: "app-update-password", templateUrl: "update-password.component.html", + standalone: false, }) export class UpdatePasswordComponent extends BaseUpdatePasswordComponent { private routerService = inject(RouterService); diff --git a/apps/web/src/app/auth/update-temp-password.component.ts b/apps/web/src/app/auth/update-temp-password.component.ts index f7d952b97f4..ead10660b92 100644 --- a/apps/web/src/app/auth/update-temp-password.component.ts +++ b/apps/web/src/app/auth/update-temp-password.component.ts @@ -5,5 +5,6 @@ import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from " @Component({ selector: "app-update-temp-password", templateUrl: "update-temp-password.component.html", + standalone: false, }) export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {} diff --git a/apps/web/src/app/auth/verify-email-token.component.ts b/apps/web/src/app/auth/verify-email-token.component.ts index 55569f44c10..9e44cc7a713 100644 --- a/apps/web/src/app/auth/verify-email-token.component.ts +++ b/apps/web/src/app/auth/verify-email-token.component.ts @@ -15,6 +15,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-verify-email-token", templateUrl: "verify-email-token.component.html", + standalone: false, }) export class VerifyEmailTokenComponent implements OnInit { constructor( diff --git a/apps/web/src/app/auth/verify-recover-delete.component.ts b/apps/web/src/app/auth/verify-recover-delete.component.ts index 8d95dd01b77..a475fdfd3e5 100644 --- a/apps/web/src/app/auth/verify-recover-delete.component.ts +++ b/apps/web/src/app/auth/verify-recover-delete.component.ts @@ -14,6 +14,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-verify-recover-delete", templateUrl: "verify-recover-delete.component.html", + standalone: false, }) export class VerifyRecoverDeleteComponent implements OnInit { email: string; diff --git a/apps/web/src/app/billing/individual/billing-history-view.component.ts b/apps/web/src/app/billing/individual/billing-history-view.component.ts index 89e6b9ca9c3..d615e01d0db 100644 --- a/apps/web/src/app/billing/individual/billing-history-view.component.ts +++ b/apps/web/src/app/billing/individual/billing-history-view.component.ts @@ -12,6 +12,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl @Component({ templateUrl: "billing-history-view.component.html", + standalone: false, }) export class BillingHistoryViewComponent implements OnInit { loading = false; diff --git a/apps/web/src/app/billing/individual/premium/premium.component.ts b/apps/web/src/app/billing/individual/premium/premium.component.ts index 2934e69f0ad..974c22455ff 100644 --- a/apps/web/src/app/billing/individual/premium/premium.component.ts +++ b/apps/web/src/app/billing/individual/premium/premium.component.ts @@ -25,6 +25,7 @@ import { TaxInfoComponent } from "../../shared/tax-info.component"; @Component({ templateUrl: "./premium.component.html", + standalone: false, }) export class PremiumComponent { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; diff --git a/apps/web/src/app/billing/individual/subscription.component.ts b/apps/web/src/app/billing/individual/subscription.component.ts index edd16ca81fe..2a08ec85127 100644 --- a/apps/web/src/app/billing/individual/subscription.component.ts +++ b/apps/web/src/app/billing/individual/subscription.component.ts @@ -9,6 +9,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl @Component({ templateUrl: "subscription.component.html", + standalone: false, }) export class SubscriptionComponent implements OnInit { hasPremium$: Observable; diff --git a/apps/web/src/app/billing/individual/user-subscription.component.ts b/apps/web/src/app/billing/individual/user-subscription.component.ts index 38f4436fb47..4d1fa97785b 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -28,6 +28,7 @@ import { UpdateLicenseDialogResult } from "../shared/update-license-types"; @Component({ templateUrl: "user-subscription.component.html", + standalone: false, }) export class UserSubscriptionComponent implements OnInit { loading = false; diff --git a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts index 02e4e6a9cc1..dddc730168c 100644 --- a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts +++ b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts @@ -23,6 +23,7 @@ import { AddSponsorshipDialogComponent } from "./add-sponsorship-dialog.componen @Component({ selector: "app-free-bitwarden-families", templateUrl: "free-bitwarden-families.component.html", + standalone: false, }) export class FreeBitwardenFamiliesComponent implements OnInit { loading = signal(true); diff --git a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts index 786c25c8d4c..d1086a6646b 100644 --- a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts @@ -19,6 +19,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-adjust-subscription", templateUrl: "adjust-subscription.component.html", + standalone: false, }) export class AdjustSubscription implements OnInit, OnDestroy { @Input() organizationId: string; diff --git a/apps/web/src/app/billing/organizations/billing-sync-api-key.component.ts b/apps/web/src/app/billing/organizations/billing-sync-api-key.component.ts index 1eb1c7124ad..55687f00052 100644 --- a/apps/web/src/app/billing/organizations/billing-sync-api-key.component.ts +++ b/apps/web/src/app/billing/organizations/billing-sync-api-key.component.ts @@ -22,6 +22,7 @@ export interface BillingSyncApiModalData { @Component({ templateUrl: "billing-sync-api-key.component.html", + standalone: false, }) export class BillingSyncApiKeyComponent { protected organizationId: string; diff --git a/apps/web/src/app/billing/organizations/billing-sync-key.component.ts b/apps/web/src/app/billing/organizations/billing-sync-key.component.ts index f29a98b2612..37ebefc803a 100644 --- a/apps/web/src/app/billing/organizations/billing-sync-key.component.ts +++ b/apps/web/src/app/billing/organizations/billing-sync-key.component.ts @@ -21,6 +21,7 @@ export interface BillingSyncKeyModalData { @Component({ templateUrl: "billing-sync-key.component.html", + standalone: false, }) export class BillingSyncKeyComponent { protected entityId: string; diff --git a/apps/web/src/app/billing/organizations/change-plan.component.ts b/apps/web/src/app/billing/organizations/change-plan.component.ts index 7c25413079a..31cbf4e94bf 100644 --- a/apps/web/src/app/billing/organizations/change-plan.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan.component.ts @@ -9,6 +9,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" @Component({ selector: "app-change-plan", templateUrl: "change-plan.component.html", + standalone: false, }) export class ChangePlanComponent { @Input() organizationId: string; diff --git a/apps/web/src/app/billing/organizations/download-license.component.ts b/apps/web/src/app/billing/organizations/download-license.component.ts index 66778aec50f..8ada57e8377 100644 --- a/apps/web/src/app/billing/organizations/download-license.component.ts +++ b/apps/web/src/app/billing/organizations/download-license.component.ts @@ -20,6 +20,7 @@ type DownloadLicenseDialogData = { @Component({ templateUrl: "download-license.component.html", + standalone: false, }) export class DownloadLicenceDialogComponent { licenseForm = this.formBuilder.group({ diff --git a/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts b/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts index d533badabf8..ce4678ad8ef 100644 --- a/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts +++ b/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts @@ -12,6 +12,7 @@ import { @Component({ templateUrl: "organization-billing-history-view.component.html", + standalone: false, }) export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy { loading = false; diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts index 5cbaf940695..792a138c2d1 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts @@ -44,6 +44,7 @@ import { SecretsManagerSubscriptionOptions } from "./sm-adjust-subscription.comp @Component({ templateUrl: "organization-subscription-cloud.component.html", + standalone: false, }) export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy { static readonly QUERY_PARAM_UPGRADE: string = "upgrade"; diff --git a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts index 2189bfa830f..fa4b633cb7a 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts @@ -36,6 +36,7 @@ enum LicenseOptions { @Component({ templateUrl: "organization-subscription-selfhost.component.html", + standalone: false, }) export class OrganizationSubscriptionSelfhostComponent implements OnInit, OnDestroy { subscription: SelfHostedOrganizationSubscriptionView; diff --git a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts index b4064bb1d87..c896ee6404c 100644 --- a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts +++ b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts @@ -37,6 +37,7 @@ import { FreeTrial } from "../../types/free-trial"; @Component({ templateUrl: "./organization-payment-method.component.html", + standalone: false, }) export class OrganizationPaymentMethodComponent implements OnDestroy { organizationId: string; diff --git a/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts index c10c9abc9b6..33413832865 100644 --- a/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts @@ -59,6 +59,7 @@ export interface SecretsManagerSubscriptionOptions { @Component({ selector: "app-sm-adjust-subscription", templateUrl: "sm-adjust-subscription.component.html", + standalone: false, }) export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDestroy { @Input() organizationId: string; diff --git a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts index 617b68abb37..6f9525e4fce 100644 --- a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts +++ b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts @@ -23,6 +23,7 @@ import { secretsManagerSubscribeFormFactory } from "../shared"; @Component({ selector: "sm-subscribe-standalone", templateUrl: "sm-subscribe-standalone.component.html", + standalone: false, }) export class SecretsManagerSubscribeStandaloneComponent { @Input() plan: PlanResponse; diff --git a/apps/web/src/app/billing/organizations/subscription-hidden.component.ts b/apps/web/src/app/billing/organizations/subscription-hidden.component.ts index 894db727b01..1455d76d67e 100644 --- a/apps/web/src/app/billing/organizations/subscription-hidden.component.ts +++ b/apps/web/src/app/billing/organizations/subscription-hidden.component.ts @@ -34,6 +34,7 @@ const SubscriptionHiddenIcon = svgIcon`

{{ "billingManagedByProvider" | i18n: providerName }}

{{ "billingContactProviderForAssistance" | i18n }}

`, + standalone: false, }) export class SubscriptionHiddenComponent { @Input() providerName: string; diff --git a/apps/web/src/app/billing/organizations/subscription-status.component.ts b/apps/web/src/app/billing/organizations/subscription-status.component.ts index a097bf674e2..0b59df3f707 100644 --- a/apps/web/src/app/billing/organizations/subscription-status.component.ts +++ b/apps/web/src/app/billing/organizations/subscription-status.component.ts @@ -26,6 +26,7 @@ type ComponentData = { @Component({ selector: "app-subscription-status", templateUrl: "subscription-status.component.html", + standalone: false, }) export class SubscriptionStatusComponent { @Input({ required: true }) organizationSubscriptionResponse: OrganizationSubscriptionResponse; diff --git a/apps/web/src/app/billing/settings/sponsored-families.component.ts b/apps/web/src/app/billing/settings/sponsored-families.component.ts index 7e493168ce2..80e66784ae8 100644 --- a/apps/web/src/app/billing/settings/sponsored-families.component.ts +++ b/apps/web/src/app/billing/settings/sponsored-families.component.ts @@ -36,6 +36,7 @@ interface RequestSponsorshipForm { @Component({ selector: "app-sponsored-families", templateUrl: "sponsored-families.component.html", + standalone: false, }) export class SponsoredFamiliesComponent implements OnInit, OnDestroy { loading = false; diff --git a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts index 39a7531082a..70320e7e62e 100644 --- a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts +++ b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts @@ -18,6 +18,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "[sponsoring-org-row]", templateUrl: "sponsoring-org-row.component.html", + standalone: false, }) export class SponsoringOrgRowComponent implements OnInit { @Input() sponsoringOrg: Organization = null; diff --git a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts b/apps/web/src/app/billing/shared/add-credit-dialog.component.ts index ec6e251418b..cdf72168acf 100644 --- a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts +++ b/apps/web/src/app/billing/shared/add-credit-dialog.component.ts @@ -35,6 +35,7 @@ export type PayPalConfig = { @Component({ templateUrl: "add-credit-dialog.component.html", + standalone: false, }) export class AddCreditDialogComponent implements OnInit { @ViewChild("ppButtonForm", { read: ElementRef, static: true }) ppButtonFormRef: ElementRef; diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts index 9d32becd1bb..94929c58656 100644 --- a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts +++ b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts @@ -39,6 +39,7 @@ export enum AdjustPaymentDialogResultType { @Component({ templateUrl: "./adjust-payment-dialog.component.html", + standalone: false, }) export class AdjustPaymentDialogComponent implements OnInit { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; diff --git a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts index 6cd17218f02..1f9172eaf59 100644 --- a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts +++ b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts @@ -31,6 +31,7 @@ export enum AdjustStorageDialogResultType { @Component({ templateUrl: "./adjust-storage-dialog.component.html", + standalone: false, }) export class AdjustStorageDialogComponent { protected formGroup = new FormGroup({ diff --git a/apps/web/src/app/billing/shared/billing-history.component.ts b/apps/web/src/app/billing/shared/billing-history.component.ts index 1994c53a375..745939f0d5e 100644 --- a/apps/web/src/app/billing/shared/billing-history.component.ts +++ b/apps/web/src/app/billing/shared/billing-history.component.ts @@ -11,6 +11,7 @@ import { @Component({ selector: "app-billing-history", templateUrl: "billing-history.component.html", + standalone: false, }) export class BillingHistoryComponent { @Input() diff --git a/apps/web/src/app/billing/shared/offboarding-survey.component.ts b/apps/web/src/app/billing/shared/offboarding-survey.component.ts index 62213c1fe94..9f21f2b8cd5 100644 --- a/apps/web/src/app/billing/shared/offboarding-survey.component.ts +++ b/apps/web/src/app/billing/shared/offboarding-survey.component.ts @@ -49,6 +49,7 @@ export const openOffboardingSurvey = ( @Component({ selector: "app-cancel-subscription-form", templateUrl: "offboarding-survey.component.html", + standalone: false, }) export class OffboardingSurveyComponent { protected ResultType = OffboardingSurveyDialogResultType; diff --git a/apps/web/src/app/billing/shared/payment-method.component.ts b/apps/web/src/app/billing/shared/payment-method.component.ts index f7d4480c4d4..27c9caf7186 100644 --- a/apps/web/src/app/billing/shared/payment-method.component.ts +++ b/apps/web/src/app/billing/shared/payment-method.component.ts @@ -35,6 +35,7 @@ import { @Component({ templateUrl: "payment-method.component.html", + standalone: false, }) export class PaymentMethodComponent implements OnInit, OnDestroy { loading = false; diff --git a/apps/web/src/app/billing/shared/self-hosting-license-uploader/individual-self-hosting-license-uploader.component.ts b/apps/web/src/app/billing/shared/self-hosting-license-uploader/individual-self-hosting-license-uploader.component.ts index 0d7698e00fd..75da10a7b09 100644 --- a/apps/web/src/app/billing/shared/self-hosting-license-uploader/individual-self-hosting-license-uploader.component.ts +++ b/apps/web/src/app/billing/shared/self-hosting-license-uploader/individual-self-hosting-license-uploader.component.ts @@ -17,6 +17,7 @@ import { AbstractSelfHostingLicenseUploaderComponent } from "../../shared/self-h @Component({ selector: "individual-self-hosting-license-uploader", templateUrl: "./self-hosting-license-uploader.component.html", + standalone: false, }) export class IndividualSelfHostingLicenseUploaderComponent extends AbstractSelfHostingLicenseUploaderComponent { /** diff --git a/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts b/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts index 222aff3fec6..1850b5e526d 100644 --- a/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts +++ b/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts @@ -24,6 +24,7 @@ import { AbstractSelfHostingLicenseUploaderComponent } from "../../shared/self-h @Component({ selector: "organization-self-hosting-license-uploader", templateUrl: "./self-hosting-license-uploader.component.html", + standalone: false, }) export class OrganizationSelfHostingLicenseUploaderComponent extends AbstractSelfHostingLicenseUploaderComponent { /** diff --git a/apps/web/src/app/billing/shared/sm-subscribe.component.ts b/apps/web/src/app/billing/shared/sm-subscribe.component.ts index 23041ccb1ae..1ecf3648bd2 100644 --- a/apps/web/src/app/billing/shared/sm-subscribe.component.ts +++ b/apps/web/src/app/billing/shared/sm-subscribe.component.ts @@ -33,6 +33,7 @@ export const secretsManagerSubscribeFormFactory = ( @Component({ selector: "sm-subscribe", templateUrl: "sm-subscribe.component.html", + standalone: false, }) export class SecretsManagerSubscribeComponent implements OnInit, OnDestroy { @Input() formGroup: FormGroup>; diff --git a/apps/web/src/app/billing/shared/update-license-dialog.component.ts b/apps/web/src/app/billing/shared/update-license-dialog.component.ts index afc62b0ca31..11b5e7fd8df 100644 --- a/apps/web/src/app/billing/shared/update-license-dialog.component.ts +++ b/apps/web/src/app/billing/shared/update-license-dialog.component.ts @@ -12,6 +12,7 @@ import { UpdateLicenseComponent } from "./update-license.component"; @Component({ templateUrl: "update-license-dialog.component.html", + standalone: false, }) export class UpdateLicenseDialogComponent extends UpdateLicenseComponent { constructor( diff --git a/apps/web/src/app/billing/shared/update-license.component.ts b/apps/web/src/app/billing/shared/update-license.component.ts index e580d420202..455b38386c6 100644 --- a/apps/web/src/app/billing/shared/update-license.component.ts +++ b/apps/web/src/app/billing/shared/update-license.component.ts @@ -15,6 +15,7 @@ import { UpdateLicenseDialogResult } from "./update-license-types"; @Component({ selector: "app-update-license", templateUrl: "update-license.component.html", + standalone: false, }) export class UpdateLicenseComponent implements OnInit { @Input() organizationId: string; diff --git a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts index 90df6e513c4..93f2bc021cd 100644 --- a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts +++ b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts @@ -47,6 +47,7 @@ export type InitiationPath = @Component({ selector: "app-complete-trial-initiation", templateUrl: "complete-trial-initiation.component.html", + standalone: false, }) export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { @ViewChild("stepper", { static: false }) verticalStepper: VerticalStepperComponent; diff --git a/apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts b/apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts index e88c49c4f57..cbb1c84284c 100644 --- a/apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts +++ b/apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts @@ -7,6 +7,7 @@ import { ProductType } from "@bitwarden/common/billing/enums"; @Component({ selector: "app-trial-confirmation-details", templateUrl: "confirmation-details.component.html", + standalone: false, }) export class ConfirmationDetailsComponent { @Input() email: string; diff --git a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.ts b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.ts index e43eb4e6cda..0c6e084f5c4 100644 --- a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.ts +++ b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.ts @@ -7,6 +7,7 @@ import { VerticalStep } from "./vertical-step.component"; @Component({ selector: "app-vertical-step-content", templateUrl: "vertical-step-content.component.html", + standalone: false, }) export class VerticalStepContentComponent { @Output() onSelectStep = new EventEmitter(); diff --git a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts index 1ff900875df..b4b643b3889 100644 --- a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts +++ b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts @@ -5,6 +5,7 @@ import { Component, Input } from "@angular/core"; selector: "app-vertical-step", templateUrl: "vertical-step.component.html", providers: [{ provide: CdkStep, useExisting: VerticalStep }], + standalone: false, }) export class VerticalStep extends CdkStep { @Input() subLabel = ""; diff --git a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.ts b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.ts index 04197827813..333224aac54 100644 --- a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.ts +++ b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.ts @@ -9,6 +9,7 @@ import { VerticalStep } from "./vertical-step.component"; selector: "app-vertical-stepper", templateUrl: "vertical-stepper.component.html", providers: [{ provide: CdkStepper, useExisting: VerticalStepperComponent }], + standalone: false, }) export class VerticalStepperComponent extends CdkStepper { readonly steps: QueryList; diff --git a/apps/web/src/app/components/environment-selector/environment-selector.component.ts b/apps/web/src/app/components/environment-selector/environment-selector.component.ts index b86c068911f..3f71a0d719a 100644 --- a/apps/web/src/app/components/environment-selector/environment-selector.component.ts +++ b/apps/web/src/app/components/environment-selector/environment-selector.component.ts @@ -13,6 +13,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Component({ selector: "environment-selector", templateUrl: "environment-selector.component.html", + standalone: false, }) export class EnvironmentSelectorComponent implements OnInit { constructor( diff --git a/apps/web/src/app/dirt/reports/pages/breach-report.component.ts b/apps/web/src/app/dirt/reports/pages/breach-report.component.ts index e1da7be06f8..47bf0844468 100644 --- a/apps/web/src/app/dirt/reports/pages/breach-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/breach-report.component.ts @@ -11,6 +11,7 @@ import { BreachAccountResponse } from "@bitwarden/common/models/response/breach- @Component({ selector: "app-breach-report", templateUrl: "breach-report.component.html", + standalone: false, }) export class BreachReportComponent implements OnInit { loading = false; diff --git a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts index 900ff3703f0..5710ea1176e 100644 --- a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts @@ -20,6 +20,7 @@ type ReportResult = CipherView & { exposedXTimes: number }; @Component({ selector: "app-exposed-passwords-report", templateUrl: "exposed-passwords-report.component.html", + standalone: false, }) export class ExposedPasswordsReportComponent extends CipherReportComponent implements OnInit { disabled = true; diff --git a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts index 5265326128e..95810625dac 100644 --- a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts @@ -21,6 +21,7 @@ import { CipherReportComponent } from "./cipher-report.component"; @Component({ selector: "app-inactive-two-factor-report", templateUrl: "inactive-two-factor-report.component.html", + standalone: false, }) export class InactiveTwoFactorReportComponent extends CipherReportComponent implements OnInit { services = new Map(); diff --git a/apps/web/src/app/dirt/reports/pages/organizations/exposed-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/exposed-passwords-report.component.ts index 4f0988082b4..b88987e1d25 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/exposed-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/exposed-passwords-report.component.ts @@ -36,6 +36,7 @@ import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], + standalone: false, }) export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportComponent diff --git a/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts index 6dc202de0b3..1105e814245 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts @@ -35,6 +35,7 @@ import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponen RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], + standalone: false, }) export class InactiveTwoFactorReportComponent extends BaseInactiveTwoFactorReportComponent diff --git a/apps/web/src/app/dirt/reports/pages/organizations/reused-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/reused-passwords-report.component.ts index 4e37f53ba61..7fcf3562437 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/reused-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/reused-passwords-report.component.ts @@ -35,6 +35,7 @@ import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent } RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], + standalone: false, }) export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportComponent diff --git a/apps/web/src/app/dirt/reports/pages/organizations/unsecured-websites-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/unsecured-websites-report.component.ts index 25e1314fceb..2e916da0294 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/unsecured-websites-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/unsecured-websites-report.component.ts @@ -35,6 +35,7 @@ import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponen RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], + standalone: false, }) export class UnsecuredWebsitesReportComponent extends BaseUnsecuredWebsitesReportComponent diff --git a/apps/web/src/app/dirt/reports/pages/organizations/weak-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/weak-passwords-report.component.ts index ef9bd97008e..80be66e9ad2 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/weak-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/weak-passwords-report.component.ts @@ -36,6 +36,7 @@ import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], + standalone: false, }) export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportComponent diff --git a/apps/web/src/app/dirt/reports/pages/reports-home.component.ts b/apps/web/src/app/dirt/reports/pages/reports-home.component.ts index 604d66f6858..acc3efac58a 100644 --- a/apps/web/src/app/dirt/reports/pages/reports-home.component.ts +++ b/apps/web/src/app/dirt/reports/pages/reports-home.component.ts @@ -12,6 +12,7 @@ import { ReportEntry, ReportVariant } from "../shared"; @Component({ selector: "app-reports-home", templateUrl: "reports-home.component.html", + standalone: false, }) export class ReportsHomeComponent implements OnInit { reports: ReportEntry[]; diff --git a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts index 6d70cc23875..3e9abc779ba 100644 --- a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts @@ -19,6 +19,7 @@ import { CipherReportComponent } from "./cipher-report.component"; @Component({ selector: "app-reused-passwords-report", templateUrl: "reused-passwords-report.component.html", + standalone: false, }) export class ReusedPasswordsReportComponent extends CipherReportComponent implements OnInit { passwordUseMap: Map; diff --git a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts index 02d4117c684..d2cc792198e 100644 --- a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts @@ -18,6 +18,7 @@ import { CipherReportComponent } from "./cipher-report.component"; @Component({ selector: "app-unsecured-websites-report", templateUrl: "unsecured-websites-report.component.html", + standalone: false, }) export class UnsecuredWebsitesReportComponent extends CipherReportComponent implements OnInit { disabled = true; diff --git a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts index 4144c9ac20f..1716a98190c 100644 --- a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts @@ -28,6 +28,7 @@ type ReportResult = CipherView & { score: number; reportValue: ReportScore; scor @Component({ selector: "app-weak-passwords-report", templateUrl: "weak-passwords-report.component.html", + standalone: false, }) export class WeakPasswordsReportComponent extends CipherReportComponent implements OnInit { disabled = true; diff --git a/apps/web/src/app/dirt/reports/reports-layout.component.ts b/apps/web/src/app/dirt/reports/reports-layout.component.ts index 7bfe912c1ad..360898e6057 100644 --- a/apps/web/src/app/dirt/reports/reports-layout.component.ts +++ b/apps/web/src/app/dirt/reports/reports-layout.component.ts @@ -6,6 +6,7 @@ import { filter } from "rxjs/operators"; @Component({ selector: "app-reports-layout", templateUrl: "reports-layout.component.html", + standalone: false, }) export class ReportsLayoutComponent implements OnDestroy { homepage = true; diff --git a/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts b/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts index da42d92bf84..92e6ddb0028 100644 --- a/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts +++ b/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts @@ -9,6 +9,7 @@ import { ReportVariant } from "../models/report-variant"; @Component({ selector: "app-report-card", templateUrl: "report-card.component.html", + standalone: false, }) export class ReportCardComponent { @Input() title: string; diff --git a/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.ts b/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.ts index cd6b77f9c81..c81c99d50d5 100644 --- a/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.ts +++ b/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.ts @@ -7,6 +7,7 @@ import { ReportEntry } from "../models/report-entry"; @Component({ selector: "app-report-list", templateUrl: "report-list.component.html", + standalone: false, }) export class ReportListComponent { @Input() reports: ReportEntry[]; diff --git a/apps/web/src/app/key-management/key-connector/remove-password.component.ts b/apps/web/src/app/key-management/key-connector/remove-password.component.ts index 3ca9d3a5669..1b07f04ba8a 100644 --- a/apps/web/src/app/key-management/key-connector/remove-password.component.ts +++ b/apps/web/src/app/key-management/key-connector/remove-password.component.ts @@ -5,5 +5,6 @@ import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitward @Component({ selector: "app-remove-password", templateUrl: "remove-password.component.html", + standalone: false, }) export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/web/src/app/layouts/frontend-layout.component.ts b/apps/web/src/app/layouts/frontend-layout.component.ts index 609845f22cd..9e22ab6d43e 100644 --- a/apps/web/src/app/layouts/frontend-layout.component.ts +++ b/apps/web/src/app/layouts/frontend-layout.component.ts @@ -7,6 +7,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl @Component({ selector: "app-frontend-layout", templateUrl: "frontend-layout.component.html", + standalone: false, }) export class FrontendLayoutComponent implements OnInit, OnDestroy { version: string; diff --git a/apps/web/src/app/layouts/header/web-header.component.ts b/apps/web/src/app/layouts/header/web-header.component.ts index e17d059160f..67d447723e1 100644 --- a/apps/web/src/app/layouts/header/web-header.component.ts +++ b/apps/web/src/app/layouts/header/web-header.component.ts @@ -17,6 +17,7 @@ import { UserId } from "@bitwarden/common/types/guid"; @Component({ selector: "app-header", templateUrl: "./web-header.component.html", + standalone: false, }) export class WebHeaderComponent { /** diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts index 737fa2d8d2e..9d4250087af 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts @@ -6,6 +6,7 @@ import { ProductSwitcherItem, ProductSwitcherService } from "../shared/product-s @Component({ selector: "navigation-product-switcher", templateUrl: "./navigation-switcher.component.html", + standalone: false, }) export class NavigationProductSwitcherComponent { constructor(private productSwitcherService: ProductSwitcherService) {} diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index fca9063c8cf..b1c1a0a906a 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -24,6 +24,7 @@ import { NavigationProductSwitcherComponent } from "./navigation-switcher.compon @Directive({ selector: "[mockOrgs]", + standalone: false, }) class MockOrganizationService implements Partial { private static _orgs = new BehaviorSubject([]); @@ -40,6 +41,7 @@ class MockOrganizationService implements Partial { @Directive({ selector: "[mockProviders]", + standalone: false, }) class MockProviderService implements Partial { private static _providers = new BehaviorSubject([]); @@ -78,12 +80,14 @@ class MockPlatformUtilsService implements Partial { @Component({ selector: "story-layout", template: ``, + standalone: false, }) class StoryLayoutComponent {} @Component({ selector: "story-content", template: ``, + standalone: false, }) class StoryContentComponent {} diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts index 4a22f628570..5a6572e15be 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts @@ -9,6 +9,7 @@ import { ProductSwitcherService } from "./shared/product-switcher.service"; @Component({ selector: "product-switcher-content", templateUrl: "./product-switcher-content.component.html", + standalone: false, }) export class ProductSwitcherContentComponent { @ViewChild("menu") diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts index 834571e2cb4..5dd29815ef2 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts @@ -8,6 +8,7 @@ import { ProductSwitcherService } from "./shared/product-switcher.service"; @Component({ selector: "product-switcher", templateUrl: "./product-switcher.component.html", + standalone: false, }) export class ProductSwitcherComponent implements AfterViewInit { /** diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts index ca27aae4581..4525105e579 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts @@ -24,6 +24,7 @@ import { ProductSwitcherService } from "./shared/product-switcher.service"; @Directive({ selector: "[mockOrgs]", + standalone: false, }) class MockOrganizationService implements Partial { private static _orgs = new BehaviorSubject([]); @@ -40,6 +41,7 @@ class MockOrganizationService implements Partial { @Directive({ selector: "[mockProviders]", + standalone: false, }) class MockProviderService implements Partial { private static _providers = new BehaviorSubject([]); @@ -78,12 +80,14 @@ class MockPlatformUtilsService implements Partial { @Component({ selector: "story-layout", template: ``, + standalone: false, }) class StoryLayoutComponent {} @Component({ selector: "story-content", template: ``, + standalone: false, }) class StoryContentComponent {} diff --git a/apps/web/src/app/settings/domain-rules.component.ts b/apps/web/src/app/settings/domain-rules.component.ts index 6dd27cbf19b..2d570f4aeb4 100644 --- a/apps/web/src/app/settings/domain-rules.component.ts +++ b/apps/web/src/app/settings/domain-rules.component.ts @@ -12,6 +12,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl @Component({ selector: "app-domain-rules", templateUrl: "domain-rules.component.html", + standalone: false, }) export class DomainRulesComponent implements OnInit { loading = true; diff --git a/apps/web/src/app/settings/preferences.component.ts b/apps/web/src/app/settings/preferences.component.ts index 4d4e0c3d711..9ab23c76795 100644 --- a/apps/web/src/app/settings/preferences.component.ts +++ b/apps/web/src/app/settings/preferences.component.ts @@ -37,6 +37,7 @@ import { DialogService } from "@bitwarden/components"; @Component({ selector: "app-preferences", templateUrl: "preferences.component.html", + standalone: false, }) export class PreferencesComponent implements OnInit, OnDestroy { // For use in template diff --git a/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts b/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts index 7bb86c9f669..f9798ec7f0f 100644 --- a/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts +++ b/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts @@ -8,6 +8,7 @@ import { Component, Input } from "@angular/core"; host: { class: "tw-max-w-max", }, + standalone: false, }) export class OnboardingTaskComponent { @Input() diff --git a/apps/web/src/app/shared/components/onboarding/onboarding.component.ts b/apps/web/src/app/shared/components/onboarding/onboarding.component.ts index 23f9015b024..5ead9fcc10b 100644 --- a/apps/web/src/app/shared/components/onboarding/onboarding.component.ts +++ b/apps/web/src/app/shared/components/onboarding/onboarding.component.ts @@ -7,6 +7,7 @@ import { OnboardingTaskComponent } from "./onboarding-task.component"; @Component({ selector: "app-onboarding", templateUrl: "./onboarding.component.html", + standalone: false, }) export class OnboardingComponent { @ContentChildren(OnboardingTaskComponent) tasks: QueryList; diff --git a/apps/web/src/app/vault/components/premium-badge.component.ts b/apps/web/src/app/vault/components/premium-badge.component.ts index 6deff43489d..ec444404aea 100644 --- a/apps/web/src/app/vault/components/premium-badge.component.ts +++ b/apps/web/src/app/vault/components/premium-badge.component.ts @@ -9,6 +9,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag {{ "premium" | i18n }} `, + standalone: false, }) export class PremiumBadgeComponent { constructor(private messagingService: MessagingService) {} diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts index 317b02356ba..ac1774cd244 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts @@ -20,6 +20,7 @@ import { RowHeightClass } from "./vault-items.component"; @Component({ selector: "tr[appVaultCipherRow]", templateUrl: "vault-cipher-row.component.html", + standalone: false, }) export class VaultCipherRowComponent implements OnInit { protected RowHeightClass = RowHeightClass; diff --git a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts index d07ba46d136..06c78ea0351 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts @@ -18,6 +18,7 @@ import { RowHeightClass } from "./vault-items.component"; @Component({ selector: "tr[appVaultCollectionRow]", templateUrl: "vault-collection-row.component.html", + standalone: false, }) export class VaultCollectionRowComponent { protected RowHeightClass = RowHeightClass; diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts index d27f332f13f..1c63ac85d34 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts @@ -32,8 +32,7 @@ type ItemPermission = CollectionPermission | "NoAccess"; @Component({ selector: "app-vault-items", templateUrl: "vault-items.component.html", - // TODO: Improve change detection, see: https://bitwarden.atlassian.net/browse/TDL-220 - // changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, }) export class VaultItemsComponent { protected RowHeight = RowHeight; diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts index 1650b0f371f..43a44cf5066 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts @@ -53,6 +53,7 @@ export const openBulkDeleteDialog = ( @Component({ templateUrl: "bulk-delete-dialog.component.html", + standalone: false, }) export class BulkDeleteDialogComponent { cipherIds: string[]; diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts index d287c430d49..dc262b01334 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts @@ -47,6 +47,7 @@ export const openBulkMoveDialog = ( @Component({ templateUrl: "bulk-move-dialog.component.html", + standalone: false, }) export class BulkMoveDialogComponent implements OnInit { cipherIds: string[] = []; diff --git a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts index 6a3c5663d93..3050c00dd6c 100644 --- a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts @@ -23,6 +23,7 @@ import { KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-folder-add-edit", templateUrl: "folder-add-edit.component.html", + standalone: false, }) export class FolderAddEditComponent extends BaseFolderAddEditComponent { protected override componentName = "app-folder-add-edit"; diff --git a/apps/web/src/app/vault/individual-vault/organization-badge/organization-name-badge.component.ts b/apps/web/src/app/vault/individual-vault/organization-badge/organization-name-badge.component.ts index c1f935f2001..915bc00bacd 100644 --- a/apps/web/src/app/vault/individual-vault/organization-badge/organization-name-badge.component.ts +++ b/apps/web/src/app/vault/individual-vault/organization-badge/organization-name-badge.component.ts @@ -12,6 +12,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Component({ selector: "app-org-badge", templateUrl: "organization-name-badge.component.html", + standalone: false, }) export class OrganizationNameBadgeComponent implements OnChanges { @Input() organizationId?: string; diff --git a/apps/web/src/app/vault/individual-vault/pipes/get-group-name.pipe.ts b/apps/web/src/app/vault/individual-vault/pipes/get-group-name.pipe.ts index 81d3a8de749..09bce96728a 100644 --- a/apps/web/src/app/vault/individual-vault/pipes/get-group-name.pipe.ts +++ b/apps/web/src/app/vault/individual-vault/pipes/get-group-name.pipe.ts @@ -5,6 +5,7 @@ import { GroupView } from "../../../admin-console/organizations/core"; @Pipe({ name: "groupNameFromId", pure: true, + standalone: false, }) export class GetGroupNameFromIdPipe implements PipeTransform { transform(value: string, groups: GroupView[]) { diff --git a/apps/web/src/app/vault/individual-vault/pipes/get-organization-name.pipe.ts b/apps/web/src/app/vault/individual-vault/pipes/get-organization-name.pipe.ts index 4d6c0b7d8d7..bf9dc82c527 100644 --- a/apps/web/src/app/vault/individual-vault/pipes/get-organization-name.pipe.ts +++ b/apps/web/src/app/vault/individual-vault/pipes/get-organization-name.pipe.ts @@ -5,6 +5,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga @Pipe({ name: "orgNameFromId", pure: true, + standalone: false, }) export class GetOrgNameFromIdPipe implements PipeTransform { transform(value: string, organizations: Organization[]) { diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts index c7d91237578..e95ea669725 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts @@ -33,6 +33,7 @@ import { OrganizationFilter } from "../shared/models/vault-filter.type"; @Component({ selector: "app-organization-options", templateUrl: "organization-options.component.html", + standalone: false, }) export class OrganizationOptionsComponent implements OnInit, OnDestroy { protected actionPromise?: Promise; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts index 3f1e7755c8f..6b974296f21 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts @@ -38,6 +38,7 @@ import { OrganizationOptionsComponent } from "./organization-options.component"; @Component({ selector: "app-vault-filter", templateUrl: "vault-filter.component.html", + standalone: false, }) export class VaultFilterComponent implements OnInit, OnDestroy { filters?: VaultFilterList; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts index 41329319805..1a0a96fa19c 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts @@ -16,6 +16,7 @@ import { VaultFilter } from "../models/vault-filter.model"; @Component({ selector: "app-filter-section", templateUrl: "vault-filter-section.component.html", + standalone: false, }) export class VaultFilterSectionComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/vault/settings/purge-vault.component.ts b/apps/web/src/app/vault/settings/purge-vault.component.ts index 11e75c16720..0a25122788c 100644 --- a/apps/web/src/app/vault/settings/purge-vault.component.ts +++ b/apps/web/src/app/vault/settings/purge-vault.component.ts @@ -25,6 +25,7 @@ export interface PurgeVaultDialogData { @Component({ selector: "app-purge-vault", templateUrl: "purge-vault.component.html", + standalone: false, }) export class PurgeVaultComponent { organizationId: string = null; diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts index b9b7460f7ed..970a476df22 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts @@ -24,6 +24,7 @@ export interface DomainAddEditDialogData { @Component({ templateUrl: "domain-add-edit-dialog.component.html", + standalone: false, }) export class DomainAddEditDialogComponent implements OnInit, OnDestroy { private componentDestroyed$: Subject = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts index 8fc9b6bba0f..1f644f55a9f 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts @@ -35,6 +35,7 @@ import { @Component({ selector: "app-org-manage-domain-verification", templateUrl: "domain-verification.component.html", + standalone: false, }) export class DomainVerificationComponent implements OnInit, OnDestroy { private componentDestroyed$ = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/scim.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/scim.component.ts index 76bcd7383f3..de870cdbdcb 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/scim.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/scim.component.ts @@ -24,6 +24,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "app-org-manage-scim", templateUrl: "scim.component.html", + standalone: false, }) export class ScimComponent implements OnInit { loading = true; diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts index c276f25663a..61e2133d059 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts @@ -21,5 +21,6 @@ export class ActivateAutofillPolicy extends BasePolicy { @Component({ selector: "policy-activate-autofill", templateUrl: "activate-autofill.component.html", + standalone: false, }) export class ActivateAutofillPolicyComponent extends BasePolicyComponent {} diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/automatic-app-login.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/automatic-app-login.component.ts index 354192ff860..1a478fb4393 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/automatic-app-login.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/automatic-app-login.component.ts @@ -19,6 +19,7 @@ export class AutomaticAppLoginPolicy extends BasePolicy { @Component({ selector: "policy-automatic-app-login", templateUrl: "automatic-app-login.component.html", + standalone: false, }) export class AutomaticAppLoginPolicyComponent extends BasePolicyComponent { data = this.formBuilder.group({ diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/disable-personal-vault-export.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/disable-personal-vault-export.component.ts index 848b1d173a1..c274e58ccac 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/disable-personal-vault-export.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/disable-personal-vault-export.component.ts @@ -16,5 +16,6 @@ export class DisablePersonalVaultExportPolicy extends BasePolicy { @Component({ selector: "policy-disable-personal-vault-export", templateUrl: "disable-personal-vault-export.component.html", + standalone: false, }) export class DisablePersonalVaultExportPolicyComponent extends BasePolicyComponent {} diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts index 9d09ead800e..a5b9ad47f6e 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts @@ -22,6 +22,7 @@ export class MaximumVaultTimeoutPolicy extends BasePolicy { @Component({ selector: "policy-maximum-timeout", templateUrl: "maximum-vault-timeout.component.html", + standalone: false, }) export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent { vaultTimeoutActionOptions: { name: string; value: string }[]; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts index 88738aa897c..51296c52281 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts @@ -19,6 +19,7 @@ interface AddOrganizationDialogData { @Component({ templateUrl: "add-organization.component.html", + standalone: false, }) export class AddOrganizationComponent implements OnInit { protected provider: Provider; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts index d22665b432f..9f3582f84bb 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts @@ -8,6 +8,7 @@ import { OrganizationPlansComponent } from "@bitwarden/web-vault/app/billing"; @Component({ selector: "app-create-organization", templateUrl: "create-organization.component.html", + standalone: false, }) export class CreateOrganizationComponent implements OnInit { @ViewChild(OrganizationPlansComponent, { static: true }) diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts index a3ef074b975..7bfac8f4b32 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts @@ -14,6 +14,7 @@ import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept @Component({ selector: "app-accept-provider", templateUrl: "accept-provider.component.html", + standalone: false, }) export class AcceptProviderComponent extends BaseAcceptComponent { protected logo = BitwardenLogo; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts index 67f2382cf91..e21837f7226 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts @@ -35,6 +35,7 @@ export enum AddEditMemberDialogResultType { @Component({ templateUrl: "add-edit-member-dialog.component.html", + standalone: false, }) export class AddEditMemberDialogComponent { editing = false; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts index 8271869f1b4..8bbc299269d 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts @@ -29,6 +29,7 @@ type BulkConfirmDialogParams = { @Component({ templateUrl: "../../../../../../../../apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.html", + standalone: false, }) export class BulkConfirmDialogComponent extends BaseBulkConfirmComponent { providerId: string; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts index c3f6409f296..e000d918414 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts @@ -19,6 +19,7 @@ type BulkRemoveDialogParams = { @Component({ templateUrl: "../../../../../../../../apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.html", + standalone: false, }) export class BulkRemoveDialogComponent extends BaseBulkRemoveComponent { providerId: string; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts index 87f29fd91e9..2ad2ecdccbd 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts @@ -19,6 +19,7 @@ import { EventExportService } from "@bitwarden/web-vault/app/tools/event-export" @Component({ selector: "provider-events", templateUrl: "events.component.html", + standalone: false, }) export class EventsComponent extends BaseEventsComponent implements OnInit { exportFileName = "provider-events"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts index 4a184d2dd16..9cbe8115008 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts @@ -45,6 +45,7 @@ class MembersTableDataSource extends PeopleTableDataSource { @Component({ templateUrl: "members.component.html", + standalone: false, }) export class MembersComponent extends BaseMembersComponent { accessEvents = false; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts index ac2fb333fae..f3d67abd4e1 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts @@ -10,6 +10,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Component({ selector: "app-providers", templateUrl: "providers.component.html", + standalone: false, }) export class ProvidersComponent implements OnInit { providers: Provider[]; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts index e72e1c7c326..12dada12aa9 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts @@ -20,6 +20,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "provider-account", templateUrl: "account.component.html", + standalone: false, }) export class AccountComponent implements OnDestroy, OnInit { selfHosted = false; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup-provider.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup-provider.component.ts index a41c7cba362..473380ff288 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup-provider.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup-provider.component.ts @@ -7,6 +7,7 @@ import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept @Component({ selector: "app-setup-provider", templateUrl: "setup-provider.component.html", + standalone: false, }) export class SetupProviderComponent extends BaseAcceptComponent { protected logo = BitwardenLogo; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts index 0b6483b9f48..cb47b3fe28f 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts @@ -23,6 +23,7 @@ import { PaymentComponent } from "@bitwarden/web-vault/app/billing/shared/paymen @Component({ selector: "provider-setup", templateUrl: "setup.component.html", + standalone: false, }) export class SetupComponent implements OnInit, OnDestroy { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts index b27a7ddd0f4..5c0d0982fb5 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts @@ -13,6 +13,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-verify-recover-delete-provider", templateUrl: "verify-recover-delete-provider.component.html", + standalone: false, }) export class VerifyRecoverDeleteProviderComponent implements OnInit { name: string; diff --git a/bitwarden_license/bit-web/src/app/app.component.ts b/bitwarden_license/bit-web/src/app/app.component.ts index dd814f5c0d2..2d0dfd967a1 100644 --- a/bitwarden_license/bit-web/src/app/app.component.ts +++ b/bitwarden_license/bit-web/src/app/app.component.ts @@ -12,6 +12,7 @@ import { FreeFamiliesSponsorshipPolicy } from "./billing/policies/free-families- @Component({ selector: "app-root", templateUrl: "../../../../apps/web/src/app/app.component.html", + standalone: false, }) export class AppComponent extends BaseAppComponent implements OnInit { ngOnInit() { diff --git a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts index 03e09f8de4b..f0761757c65 100644 --- a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts +++ b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts @@ -51,6 +51,7 @@ const defaultSigningAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha2 @Component({ selector: "app-org-manage-sso", templateUrl: "sso.component.html", + standalone: false, }) export class SsoComponent implements OnInit, OnDestroy { readonly ssoType = SsoType; diff --git a/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts b/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts index 521f8658890..53d2b0ab66c 100644 --- a/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts @@ -16,5 +16,6 @@ export class FreeFamiliesSponsorshipPolicy extends BasePolicy { @Component({ selector: "policy-personal-ownership", templateUrl: "free-families-sponsorship.component.html", + standalone: false, }) export class FreeFamiliesSponsorshipPolicyComponent extends BasePolicyComponent {} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/billing-history/provider-billing-history.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/billing-history/provider-billing-history.component.ts index fa3a617b45f..d1a9d43a6fc 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/billing-history/provider-billing-history.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/billing-history/provider-billing-history.component.ts @@ -12,6 +12,7 @@ import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/ser @Component({ templateUrl: "./provider-billing-history.component.html", + standalone: false, }) export class ProviderBillingHistoryComponent { private providerId: string; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts index 4bb2c36ef15..f0eda893d67 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts @@ -27,6 +27,7 @@ export enum AddExistingOrganizationDialogResultType { @Component({ templateUrl: "./add-existing-organization-dialog.component.html", + standalone: false, }) export class AddExistingOrganizationDialogComponent implements OnInit { protected loading: boolean = true; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts index e87ee6a1187..c7d82c3ec09 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts @@ -99,6 +99,7 @@ export class PlanCard { @Component({ templateUrl: "./create-client-dialog.component.html", + standalone: false, }) export class CreateClientDialogComponent implements OnInit { protected discountPercentage: number | null | undefined; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts index 45abeab1f4a..06d1f80c101 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts @@ -41,6 +41,7 @@ export const openManageClientNameDialog = ( @Component({ templateUrl: "manage-client-name-dialog.component.html", + standalone: false, }) export class ManageClientNameDialogComponent { protected ResultType = ManageClientNameDialogResultType; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts index ced48bfdbea..71e549b563b 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts @@ -36,6 +36,7 @@ export const openManageClientSubscriptionDialog = ( @Component({ templateUrl: "./manage-client-subscription-dialog.component.html", + standalone: false, }) export class ManageClientSubscriptionDialogComponent implements OnInit { protected loading = true; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts index f262ba1abd0..056339b6fb7 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts @@ -19,6 +19,7 @@ import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept @Component({ templateUrl: "./setup-business-unit.component.html", + standalone: false, }) export class SetupBusinessUnitComponent extends BaseAcceptComponent { protected bitwardenLogo = BitwardenLogo; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts index c49509427b8..974dc9c460f 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts @@ -26,6 +26,7 @@ type ComponentData = { @Component({ selector: "app-provider-subscription-status", templateUrl: "provider-subscription-status.component.html", + standalone: false, }) export class ProviderSubscriptionStatusComponent { @Input({ required: true }) subscription: ProviderSubscriptionResponse; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts index 657d16d9380..74368ef7839 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts @@ -26,6 +26,7 @@ import { @Component({ selector: "app-provider-subscription", templateUrl: "./provider-subscription.component.html", + standalone: false, }) export class ProviderSubscriptionComponent implements OnInit, OnDestroy { private providerId: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts index 2d0a681460a..b563591f32f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts @@ -19,12 +19,14 @@ import { IntegrationsComponent } from "./integrations.component"; @Component({ selector: "app-header", template: "
", + standalone: false, }) class MockHeaderComponent {} @Component({ selector: "sm-new-menu", template: "
", + standalone: false, }) class MockNewMenuComponent {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts index cdae129de4f..01a2edb5d5d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts @@ -6,6 +6,7 @@ import { Integration } from "@bitwarden/web-vault/app/admin-console/organization @Component({ selector: "sm-integrations", templateUrl: "./integrations.component.html", + standalone: false, }) export class IntegrationsComponent { private integrationsAndSdks: Integration[] = []; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.ts index 6a90eb2a78f..b50e586c337 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.ts @@ -3,6 +3,7 @@ import { Component, OnInit } from "@angular/core"; @Component({ selector: "sm-layout", templateUrl: "./layout.component.html", + standalone: false, }) export class LayoutComponent implements OnInit { ngOnInit() { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts index 6594b71a14c..1eef528b639 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts @@ -34,6 +34,7 @@ import { CountService } from "../shared/counts/count.service"; @Component({ selector: "sm-navigation", templateUrl: "./navigation.component.html", + standalone: false, }) export class NavigationComponent implements OnInit, OnDestroy { protected readonly logo = SecretsManagerLogo; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts index 698448c2d06..a96f9a08919 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts @@ -88,6 +88,7 @@ type OrganizationTasks = { @Component({ selector: "sm-overview", templateUrl: "./overview.component.html", + standalone: false, }) export class OverviewComponent implements OnInit, OnDestroy { private destroy$: Subject = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.ts index bd68e58ea87..6b71c81f09e 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.ts @@ -3,6 +3,7 @@ import { Component, Input } from "@angular/core"; @Component({ selector: "sm-section", templateUrl: "./section.component.html", + standalone: false, }) export class SectionComponent { @Input() open = true; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts index d6ca31399e1..8cdb1bb4d69 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts @@ -27,6 +27,7 @@ export interface ProjectDeleteOperation { @Component({ templateUrl: "./project-delete-dialog.component.html", + standalone: false, }) export class ProjectDeleteDialogComponent implements OnInit { formGroup = new FormGroup({ diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts index c96887cc9ac..ec420d653cc 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts @@ -27,6 +27,7 @@ export interface ProjectOperation { @Component({ templateUrl: "./project-dialog.component.html", + standalone: false, }) export class ProjectDialogComponent implements OnInit { protected formGroup = new FormGroup({ diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts index 159b90d5432..7523fa14a21 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts @@ -22,11 +22,13 @@ import { projectAccessGuard } from "./project-access.guard"; @Component({ template: "", + standalone: false, }) export class GuardedRouteTestComponent {} @Component({ template: "", + standalone: false, }) export class RedirectTestComponent {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts index 4a0c37cb4a9..13f80920558 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts @@ -27,6 +27,7 @@ import { AccessPolicyService } from "../../shared/access-policies/access-policy. @Component({ selector: "sm-project-people", templateUrl: "./project-people.component.html", + standalone: false, }) export class ProjectPeopleComponent implements OnInit, OnDestroy { private currentAccessPolicies: ApItemViewType[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts index f4950bf53f2..536739715ff 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts @@ -45,6 +45,7 @@ import { ProjectService } from "../project.service"; @Component({ selector: "sm-project-secrets", templateUrl: "./project-secrets.component.html", + standalone: false, }) export class ProjectSecretsComponent implements OnInit { secrets$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts index d289f9f7b1f..fc3a489bce9 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts @@ -25,6 +25,7 @@ import { AccessPolicyService } from "../../shared/access-policies/access-policy. @Component({ selector: "sm-project-service-accounts", templateUrl: "./project-service-accounts.component.html", + standalone: false, }) export class ProjectServiceAccountsComponent implements OnInit, OnDestroy { private currentAccessPolicies: ApItemViewType[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts index 9e60cf0535c..2d008dd219a 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts @@ -37,6 +37,7 @@ import { ProjectService } from "../project.service"; @Component({ selector: "sm-project", templateUrl: "./project.component.html", + standalone: false, }) export class ProjectComponent implements OnInit, OnDestroy { protected project$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts index e09aaea6e2b..ea5294624af 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts @@ -40,6 +40,7 @@ import { ProjectService } from "../project.service"; @Component({ selector: "sm-projects", templateUrl: "./projects.component.html", + standalone: false, }) export class ProjectsComponent implements OnInit { protected projects$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts index 9dfc80c9f88..6340cc42f3b 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts @@ -20,6 +20,7 @@ export interface SecretDeleteOperation { @Component({ templateUrl: "./secret-delete.component.html", + standalone: false, }) export class SecretDeleteDialogComponent { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts index 09a78e02c44..9172d44965d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts @@ -69,6 +69,7 @@ export interface SecretOperation { @Component({ templateUrl: "./secret-dialog.component.html", + standalone: false, }) export class SecretDialogComponent implements OnInit, OnDestroy { loading = true; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.ts index 7f11d69d59c..b719014a382 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.ts @@ -12,6 +12,7 @@ export interface SecretViewDialogParams { @Component({ templateUrl: "./secret-view-dialog.component.html", + standalone: false, }) export class SecretViewDialogComponent implements OnInit { protected loading = true; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts index b58173a1cc0..18ecd2e3b51 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts @@ -36,6 +36,7 @@ import { SecretService } from "./secret.service"; @Component({ selector: "sm-secrets", templateUrl: "./secrets.component.html", + standalone: false, }) export class SecretsComponent implements OnInit { protected secrets$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts index caca8c92aa8..a714729d96f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts @@ -8,6 +8,7 @@ import { AccessTokenView } from "../models/view/access-token.view"; @Component({ selector: "sm-access-list", templateUrl: "./access-list.component.html", + standalone: false, }) export class AccessListComponent { @Input() diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts index a4f0c077efd..b9643ce8fd8 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts @@ -27,6 +27,7 @@ import { AccessTokenCreateDialogComponent } from "./dialogs/access-token-create- @Component({ selector: "sm-access-tokens", templateUrl: "./access-tokens.component.html", + standalone: false, }) export class AccessTokenComponent implements OnInit, OnDestroy { accessTokens$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts index 140e357edac..dfbe0a1511d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts @@ -17,6 +17,7 @@ export interface AccessTokenOperation { @Component({ templateUrl: "./access-token-create-dialog.component.html", + standalone: false, }) export class AccessTokenCreateDialogComponent implements OnInit { protected formGroup = new FormGroup({ diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts index bfc120f33e2..0259b8d6e90 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts @@ -14,6 +14,7 @@ export interface AccessTokenDetails { @Component({ templateUrl: "./access-token-dialog.component.html", + standalone: false, }) export class AccessTokenDialogComponent implements OnInit { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts index 2273b42897c..891501874ff 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts @@ -33,6 +33,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic useExisting: ExpirationOptionsComponent, }, ], + standalone: false, }) export class ExpirationOptionsComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts index 96e3b58b633..e796c4758ea 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts @@ -24,6 +24,7 @@ class ServiceAccountConfig { @Component({ selector: "sm-service-account-config", templateUrl: "./config.component.html", + standalone: false, }) export class ServiceAccountConfigComponent implements OnInit, OnDestroy { identityUrl: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts index 6f30aacccca..5edc57d8c74 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts @@ -27,6 +27,7 @@ export interface ServiceAccountDeleteOperation { @Component({ templateUrl: "./service-account-delete-dialog.component.html", + standalone: false, }) export class ServiceAccountDeleteDialogComponent { formGroup = new FormGroup({ diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts index 241c736fb7a..815ea1dc60c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts @@ -26,6 +26,7 @@ export interface ServiceAccountOperation { @Component({ templateUrl: "./service-account-dialog.component.html", + standalone: false, }) export class ServiceAccountDialogComponent implements OnInit { protected formGroup = new FormGroup( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts index 6538ae49adb..ddaa0937e6f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts @@ -18,6 +18,7 @@ import { ServiceAccountEventLogApiService } from "./service-account-event-log-ap @Component({ selector: "sm-service-accounts-events", templateUrl: "./service-accounts-events.component.html", + standalone: false, }) export class ServiceAccountEventsComponent extends BaseEventsComponent diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts index 4301b5ae81a..e0bcad8d6e9 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts @@ -22,11 +22,13 @@ import { serviceAccountAccessGuard } from "./service-account-access.guard"; @Component({ template: "", + standalone: false, }) export class GuardedRouteTestComponent {} @Component({ template: "", + standalone: false, }) export class RedirectTestComponent {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts index a57251c4f77..4449757167d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts @@ -28,6 +28,7 @@ import { AccessPolicyService } from "../../shared/access-policies/access-policy. @Component({ selector: "sm-service-account-people", templateUrl: "./service-account-people.component.html", + standalone: false, }) export class ServiceAccountPeopleComponent implements OnInit, OnDestroy { private currentAccessPolicies: ApItemViewType[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts index 34a48af3055..af334b22c63 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts @@ -25,6 +25,7 @@ import { AccessPolicyService } from "../../shared/access-policies/access-policy. @Component({ selector: "sm-service-account-projects", templateUrl: "./service-account-projects.component.html", + standalone: false, }) export class ServiceAccountProjectsComponent implements OnInit, OnDestroy { private currentAccessPolicies: ApItemViewType[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts index 74465e8ecef..5eb074e3e99 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts @@ -18,6 +18,7 @@ import { ServiceAccountService } from "./service-account.service"; @Component({ selector: "sm-service-account", templateUrl: "./service-account.component.html", + standalone: false, }) export class ServiceAccountComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts index cbffc80aa05..47d5dd63806 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts @@ -16,6 +16,7 @@ import { @Component({ selector: "sm-service-accounts-list", templateUrl: "./service-accounts-list.component.html", + standalone: false, }) export class ServiceAccountsListComponent implements OnDestroy { protected dataSource = new TableDataSource(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts index 7deaefae823..2813ece001f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts @@ -31,6 +31,7 @@ import { ServiceAccountService } from "./service-account.service"; @Component({ selector: "sm-service-accounts", templateUrl: "./service-accounts.component.html", + standalone: false, }) export class ServiceAccountsComponent implements OnInit { protected serviceAccounts$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/dialog/sm-import-error-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/dialog/sm-import-error-dialog.component.ts index 2576ca8e629..0bed0355a8c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/dialog/sm-import-error-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/dialog/sm-import-error-dialog.component.ts @@ -12,6 +12,7 @@ export interface SecretsManagerImportErrorDialogOperation { @Component({ templateUrl: "./sm-import-error-dialog.component.html", + standalone: false, }) export class SecretsManagerImportErrorDialogComponent { errorLines: SecretsManagerImportErrorLine[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts index 266962c8268..c2b726803c5 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts @@ -29,6 +29,7 @@ type ExportFormat = { @Component({ selector: "sm-export", templateUrl: "./sm-export.component.html", + standalone: false, }) export class SecretsManagerExportComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts index 262f1a6b161..65075d12bf6 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts @@ -21,6 +21,7 @@ import { SecretsManagerPortingApiService } from "../services/sm-porting-api.serv @Component({ selector: "sm-import", templateUrl: "./sm-import.component.html", + standalone: false, }) export class SecretsManagerImportComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts index 34de4e4860b..fba3ff03ee0 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts @@ -30,6 +30,7 @@ import { ApPermissionEnum } from "./models/enums/ap-permission.enum"; multi: true, }, ], + standalone: false, }) export class AccessPolicySelectorComponent implements ControlValueAccessor, OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts index 935ee1c8518..9d2a3715e16 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts @@ -25,6 +25,7 @@ export enum BulkConfirmationResult { @Component({ selector: "sm-bulk-confirmation-dialog", templateUrl: "./bulk-confirmation-dialog.component.html", + standalone: false, }) export class BulkConfirmationDialogComponent implements OnInit { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts index 0a00c7f0431..fc7890f1654 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts @@ -20,6 +20,7 @@ export class BulkOperationStatus { @Component({ templateUrl: "./bulk-status-dialog.component.html", + standalone: false, }) export class BulkStatusDialogComponent implements OnInit { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts index b0a481d077d..18823130d22 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts @@ -29,6 +29,7 @@ import { @Component({ selector: "sm-new-menu", templateUrl: "./new-menu.component.html", + standalone: false, }) export class NewMenuComponent implements OnInit, OnDestroy { private organizationId: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts index 2eb9d6017b7..89baf969c34 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts @@ -12,6 +12,7 @@ import { Icon, Icons } from "@bitwarden/components"; @Component({ templateUrl: "./org-suspended.component.html", + standalone: false, }) export class OrgSuspendedComponent { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts index 0ca5ba09074..9774172cd4b 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts @@ -13,6 +13,7 @@ import { ProjectListView } from "../models/view/project-list.view"; @Component({ selector: "sm-projects-list", templateUrl: "./projects-list.component.html", + standalone: false, }) export class ProjectsListComponent { @Input() diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts index 37b9524238f..a7ee818a01f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts @@ -15,6 +15,7 @@ import { SecretService } from "../secrets/secret.service"; @Component({ selector: "sm-secrets-list", templateUrl: "./secrets-list.component.html", + standalone: false, }) export class SecretsListComponent implements OnDestroy { protected dataSource = new TableDataSource(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts index 7c4fff4d3b3..29f9a85250c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts @@ -15,6 +15,7 @@ export interface SecretHardDeleteOperation { @Component({ templateUrl: "./secret-hard-delete.component.html", + standalone: false, }) export class SecretHardDeleteDialogComponent { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts index 6b5efe5e133..712757445be 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts @@ -15,6 +15,7 @@ export interface SecretRestoreOperation { @Component({ templateUrl: "./secret-restore.component.html", + standalone: false, }) export class SecretRestoreDialogComponent { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts index 3a21dbe3b68..4392ae8b1bb 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts @@ -24,6 +24,7 @@ import { @Component({ selector: "sm-trash", templateUrl: "./trash.component.html", + standalone: false, }) export class TrashComponent implements OnInit { secrets$: Observable; diff --git a/libs/angular/src/auth/components/environment-selector.component.ts b/libs/angular/src/auth/components/environment-selector.component.ts index 16a249dda97..e6438b3e634 100644 --- a/libs/angular/src/auth/components/environment-selector.component.ts +++ b/libs/angular/src/auth/components/environment-selector.component.ts @@ -57,6 +57,7 @@ export interface EnvironmentSelectorRouteData { transition("* => void", animate("100ms linear", style({ opacity: 0 }))), ]), ], + standalone: false, }) export class EnvironmentSelectorComponent implements OnInit, OnDestroy { @Output() onOpenSelfHostedSettings = new EventEmitter(); diff --git a/libs/angular/src/auth/components/two-factor-icon.component.ts b/libs/angular/src/auth/components/two-factor-icon.component.ts index c75078e413a..0e811595bea 100644 --- a/libs/angular/src/auth/components/two-factor-icon.component.ts +++ b/libs/angular/src/auth/components/two-factor-icon.component.ts @@ -10,6 +10,7 @@ import { WebAuthnIcon } from "../icons/webauthn.icon"; @Component({ selector: "auth-two-factor-icon", templateUrl: "./two-factor-icon.component.html", + standalone: false, }) export class TwoFactorIconComponent { @Input() provider: any; diff --git a/libs/angular/src/auth/components/user-verification.component.ts b/libs/angular/src/auth/components/user-verification.component.ts index 408d8403b88..6f5021340c7 100644 --- a/libs/angular/src/auth/components/user-verification.component.ts +++ b/libs/angular/src/auth/components/user-verification.component.ts @@ -22,6 +22,7 @@ import { KeyService } from "@bitwarden/key-management"; */ @Directive({ selector: "app-user-verification", + standalone: false, }) export class UserVerificationComponent implements ControlValueAccessor, OnInit, OnDestroy { private _invalidSecret = false; diff --git a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts index 871895c2ede..a6f1db54241 100644 --- a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts +++ b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts @@ -48,6 +48,7 @@ type PayPalConfig = { @Component({ templateUrl: "./add-account-credit-dialog.component.html", + standalone: false, }) export class AddAccountCreditDialogComponent implements OnInit { @ViewChild("payPalForm", { read: ElementRef, static: true }) payPalForm: ElementRef; diff --git a/libs/angular/src/billing/components/invoices/invoices.component.ts b/libs/angular/src/billing/components/invoices/invoices.component.ts index 8984c1afe65..fc3352048d6 100644 --- a/libs/angular/src/billing/components/invoices/invoices.component.ts +++ b/libs/angular/src/billing/components/invoices/invoices.component.ts @@ -11,6 +11,7 @@ import { FileDownloadService } from "@bitwarden/common/platform/abstractions/fil @Component({ selector: "app-invoices", templateUrl: "./invoices.component.html", + standalone: false, }) export class InvoicesComponent implements OnInit { @Input() startWith?: InvoicesResponse; diff --git a/libs/angular/src/billing/components/invoices/no-invoices.component.ts b/libs/angular/src/billing/components/invoices/no-invoices.component.ts index fffaaf65cfe..987650d1c83 100644 --- a/libs/angular/src/billing/components/invoices/no-invoices.component.ts +++ b/libs/angular/src/billing/components/invoices/no-invoices.component.ts @@ -30,6 +30,7 @@ const partnerTrustIcon = svgIcon`

{{ "noInvoicesToList" | i18n }}

`, + standalone: false, }) export class NoInvoicesComponent { icon = partnerTrustIcon; diff --git a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts index e966b4e0a75..7088d8edfcc 100644 --- a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts +++ b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts @@ -11,6 +11,7 @@ import { CountryListItem, TaxInformation } from "@bitwarden/common/billing/model @Component({ selector: "app-manage-tax-information", templateUrl: "./manage-tax-information.component.html", + standalone: false, }) export class ManageTaxInformationComponent implements OnInit, OnDestroy { @Input() startWith: TaxInformation; diff --git a/libs/angular/src/billing/directives/not-premium.directive.ts b/libs/angular/src/billing/directives/not-premium.directive.ts index 5a1c636c009..41d62bb773e 100644 --- a/libs/angular/src/billing/directives/not-premium.directive.ts +++ b/libs/angular/src/billing/directives/not-premium.directive.ts @@ -9,6 +9,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs */ @Directive({ selector: "[appNotPremium]", + standalone: false, }) export class NotPremiumDirective implements OnInit { constructor( diff --git a/libs/angular/src/components/callout.component.ts b/libs/angular/src/components/callout.component.ts index 1e5285cc3a9..215de49f676 100644 --- a/libs/angular/src/components/callout.component.ts +++ b/libs/angular/src/components/callout.component.ts @@ -12,6 +12,7 @@ import { CalloutTypes } from "@bitwarden/components"; @Component({ selector: "app-callout", templateUrl: "callout.component.html", + standalone: false, }) export class DeprecatedCalloutComponent implements OnInit { @Input() type: CalloutTypes = "info"; diff --git a/libs/angular/src/components/modal/dynamic-modal.component.ts b/libs/angular/src/components/modal/dynamic-modal.component.ts index ccbfa19868f..77491193916 100644 --- a/libs/angular/src/components/modal/dynamic-modal.component.ts +++ b/libs/angular/src/components/modal/dynamic-modal.component.ts @@ -18,6 +18,7 @@ import { ModalRef } from "./modal.ref"; @Component({ selector: "app-modal", template: "", + standalone: false, }) export class DynamicModalComponent implements AfterViewInit, OnDestroy { componentRef: ComponentRef; diff --git a/libs/angular/src/directives/a11y-invalid.directive.ts b/libs/angular/src/directives/a11y-invalid.directive.ts index 60580fcb60e..032c08d5332 100644 --- a/libs/angular/src/directives/a11y-invalid.directive.ts +++ b/libs/angular/src/directives/a11y-invalid.directive.ts @@ -6,6 +6,7 @@ import { Subscription } from "rxjs"; @Directive({ selector: "[appA11yInvalid]", + standalone: false, }) export class A11yInvalidDirective implements OnDestroy, OnInit { private sub: Subscription; diff --git a/libs/angular/src/directives/api-action.directive.ts b/libs/angular/src/directives/api-action.directive.ts index a07ab7d0413..85ba8a7489c 100644 --- a/libs/angular/src/directives/api-action.directive.ts +++ b/libs/angular/src/directives/api-action.directive.ts @@ -15,6 +15,7 @@ import { ValidationService } from "@bitwarden/common/platform/abstractions/valid */ @Directive({ selector: "[appApiAction]", + standalone: false, }) export class ApiActionDirective implements OnChanges { @Input() appApiAction: Promise; diff --git a/libs/angular/src/directives/box-row.directive.ts b/libs/angular/src/directives/box-row.directive.ts index 81fb93596f2..d36dcb7ff88 100644 --- a/libs/angular/src/directives/box-row.directive.ts +++ b/libs/angular/src/directives/box-row.directive.ts @@ -4,6 +4,7 @@ import { Directive, ElementRef, HostListener, OnInit } from "@angular/core"; @Directive({ selector: "[appBoxRow]", + standalone: false, }) export class BoxRowDirective implements OnInit { el: HTMLElement = null; diff --git a/libs/angular/src/directives/copy-text.directive.ts b/libs/angular/src/directives/copy-text.directive.ts index de26973e2c9..0f9018e19ad 100644 --- a/libs/angular/src/directives/copy-text.directive.ts +++ b/libs/angular/src/directives/copy-text.directive.ts @@ -7,6 +7,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl @Directive({ selector: "[appCopyText]", + standalone: false, }) export class CopyTextDirective { constructor( diff --git a/libs/angular/src/directives/fallback-src.directive.ts b/libs/angular/src/directives/fallback-src.directive.ts index 600782f3404..f1225245912 100644 --- a/libs/angular/src/directives/fallback-src.directive.ts +++ b/libs/angular/src/directives/fallback-src.directive.ts @@ -4,6 +4,7 @@ import { Directive, ElementRef, HostListener, Input } from "@angular/core"; @Directive({ selector: "[appFallbackSrc]", + standalone: false, }) export class FallbackSrcDirective { @Input("appFallbackSrc") appFallbackSrc: string; diff --git a/libs/angular/src/directives/if-feature.directive.ts b/libs/angular/src/directives/if-feature.directive.ts index 0186592d2d0..aa10c9e8081 100644 --- a/libs/angular/src/directives/if-feature.directive.ts +++ b/libs/angular/src/directives/if-feature.directive.ts @@ -14,6 +14,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" */ @Directive({ selector: "[appIfFeature]", + standalone: false, }) export class IfFeatureDirective implements OnInit { /** diff --git a/libs/angular/src/directives/input-strip-spaces.directive.ts b/libs/angular/src/directives/input-strip-spaces.directive.ts index 3b8fee851c0..1718aaa49d6 100644 --- a/libs/angular/src/directives/input-strip-spaces.directive.ts +++ b/libs/angular/src/directives/input-strip-spaces.directive.ts @@ -5,6 +5,7 @@ import { NgControl } from "@angular/forms"; @Directive({ selector: "input[appInputStripSpaces]", + standalone: false, }) export class InputStripSpacesDirective { constructor( diff --git a/libs/angular/src/directives/input-verbatim.directive.ts b/libs/angular/src/directives/input-verbatim.directive.ts index deecae624f1..7bd18b12659 100644 --- a/libs/angular/src/directives/input-verbatim.directive.ts +++ b/libs/angular/src/directives/input-verbatim.directive.ts @@ -4,6 +4,7 @@ import { Directive, ElementRef, Input, OnInit, Renderer2 } from "@angular/core"; @Directive({ selector: "[appInputVerbatim]", + standalone: false, }) export class InputVerbatimDirective implements OnInit { @Input() set appInputVerbatim(condition: boolean | string) { diff --git a/libs/angular/src/directives/launch-click.directive.ts b/libs/angular/src/directives/launch-click.directive.ts index e748afabf49..b270dbba5e3 100644 --- a/libs/angular/src/directives/launch-click.directive.ts +++ b/libs/angular/src/directives/launch-click.directive.ts @@ -5,6 +5,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Directive({ selector: "[appLaunchClick]", + standalone: false, }) export class LaunchClickDirective { constructor(private platformUtilsService: PlatformUtilsService) {} diff --git a/libs/angular/src/directives/stop-click.directive.ts b/libs/angular/src/directives/stop-click.directive.ts index 0e88dde33a4..58ae8082aa7 100644 --- a/libs/angular/src/directives/stop-click.directive.ts +++ b/libs/angular/src/directives/stop-click.directive.ts @@ -2,6 +2,7 @@ import { Directive, HostListener } from "@angular/core"; @Directive({ selector: "[appStopClick]", + standalone: false, }) export class StopClickDirective { @HostListener("click", ["$event"]) onClick($event: MouseEvent) { diff --git a/libs/angular/src/directives/stop-prop.directive.ts b/libs/angular/src/directives/stop-prop.directive.ts index 8393e799038..6ea9bc87bec 100644 --- a/libs/angular/src/directives/stop-prop.directive.ts +++ b/libs/angular/src/directives/stop-prop.directive.ts @@ -2,6 +2,7 @@ import { Directive, HostListener } from "@angular/core"; @Directive({ selector: "[appStopProp]", + standalone: false, }) export class StopPropDirective { @HostListener("click", ["$event"]) onClick($event: MouseEvent) { diff --git a/libs/angular/src/directives/true-false-value.directive.ts b/libs/angular/src/directives/true-false-value.directive.ts index dab88567a5c..5d25ac2a385 100644 --- a/libs/angular/src/directives/true-false-value.directive.ts +++ b/libs/angular/src/directives/true-false-value.directive.ts @@ -11,6 +11,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; multi: true, }, ], + standalone: false, }) export class TrueFalseValueDirective implements ControlValueAccessor { @Input() trueValue: boolean | string = true; diff --git a/libs/angular/src/pipes/color-password-count.pipe.ts b/libs/angular/src/pipes/color-password-count.pipe.ts index ed94e6dadff..61be6db2e2c 100644 --- a/libs/angular/src/pipes/color-password-count.pipe.ts +++ b/libs/angular/src/pipes/color-password-count.pipe.ts @@ -7,7 +7,10 @@ import { ColorPasswordPipe } from "./color-password.pipe"; /* An updated pipe that extends ColourPasswordPipe to include a character count */ -@Pipe({ name: "colorPasswordCount" }) +@Pipe({ + name: "colorPasswordCount", + standalone: false, +}) export class ColorPasswordCountPipe extends ColorPasswordPipe { transform(password: string) { const template = (character: string, type: string, index: number) => diff --git a/libs/angular/src/pipes/color-password.pipe.ts b/libs/angular/src/pipes/color-password.pipe.ts index 8407eddd9ca..6bb7bd4120d 100644 --- a/libs/angular/src/pipes/color-password.pipe.ts +++ b/libs/angular/src/pipes/color-password.pipe.ts @@ -6,7 +6,10 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; An updated pipe that sanitizes HTML, highlights numbers and special characters (in different colors each) and handles Unicode / Emoji characters correctly. */ -@Pipe({ name: "colorPassword" }) +@Pipe({ + name: "colorPassword", + standalone: false, +}) export class ColorPasswordPipe implements PipeTransform { transform(password: string) { const template = (character: string, type: string) => diff --git a/libs/angular/src/pipes/credit-card-number.pipe.ts b/libs/angular/src/pipes/credit-card-number.pipe.ts index 11500ef9510..80718612d35 100644 --- a/libs/angular/src/pipes/credit-card-number.pipe.ts +++ b/libs/angular/src/pipes/credit-card-number.pipe.ts @@ -28,7 +28,10 @@ const numberFormats: Record = { Other: [{ cardLength: 16, blocks: [4, 4, 4, 4] }], }; -@Pipe({ name: "creditCardNumber" }) +@Pipe({ + name: "creditCardNumber", + standalone: false, +}) export class CreditCardNumberPipe implements PipeTransform { transform(creditCardNumber: string, brand: string): string { let rules = numberFormats[brand]; diff --git a/libs/angular/src/pipes/search-ciphers.pipe.ts b/libs/angular/src/pipes/search-ciphers.pipe.ts index 04349e9842a..cbb595280af 100644 --- a/libs/angular/src/pipes/search-ciphers.pipe.ts +++ b/libs/angular/src/pipes/search-ciphers.pipe.ts @@ -4,6 +4,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @Pipe({ name: "searchCiphers", + standalone: false, }) export class SearchCiphersPipe implements PipeTransform { transform(ciphers: CipherView[], searchText: string, deleted = false): CipherView[] { diff --git a/libs/angular/src/pipes/search.pipe.ts b/libs/angular/src/pipes/search.pipe.ts index 9f640a5b48e..2c652bc6382 100644 --- a/libs/angular/src/pipes/search.pipe.ts +++ b/libs/angular/src/pipes/search.pipe.ts @@ -6,6 +6,7 @@ type PropertyValueFunction = (item: T) => { toString: () => string }; @Pipe({ name: "search", + standalone: false, }) export class SearchPipe implements PipeTransform { transform( diff --git a/libs/angular/src/pipes/user-name.pipe.ts b/libs/angular/src/pipes/user-name.pipe.ts index 29b884a3115..4c295c7ef13 100644 --- a/libs/angular/src/pipes/user-name.pipe.ts +++ b/libs/angular/src/pipes/user-name.pipe.ts @@ -9,6 +9,7 @@ export interface User { @Pipe({ name: "userName", + standalone: false, }) export class UserNamePipe implements PipeTransform { transform(user?: User): string { diff --git a/libs/angular/src/pipes/user-type.pipe.ts b/libs/angular/src/pipes/user-type.pipe.ts index 109abc3eeff..d9b5faa2ea9 100644 --- a/libs/angular/src/pipes/user-type.pipe.ts +++ b/libs/angular/src/pipes/user-type.pipe.ts @@ -5,6 +5,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic @Pipe({ name: "userType", + standalone: false, }) export class UserTypePipe implements PipeTransform { constructor(private i18nService: I18nService) {} diff --git a/libs/angular/src/platform/pipes/ellipsis.pipe.ts b/libs/angular/src/platform/pipes/ellipsis.pipe.ts index dd271f94627..a7050b2f037 100644 --- a/libs/angular/src/platform/pipes/ellipsis.pipe.ts +++ b/libs/angular/src/platform/pipes/ellipsis.pipe.ts @@ -2,6 +2,7 @@ import { Pipe, PipeTransform } from "@angular/core"; @Pipe({ name: "ellipsis", + standalone: false, }) /** * @deprecated Use the tailwind class 'tw-truncate' instead diff --git a/libs/angular/src/platform/pipes/fingerprint.pipe.ts b/libs/angular/src/platform/pipes/fingerprint.pipe.ts index 8f1a07cfd6b..90289ee212b 100644 --- a/libs/angular/src/platform/pipes/fingerprint.pipe.ts +++ b/libs/angular/src/platform/pipes/fingerprint.pipe.ts @@ -5,6 +5,7 @@ import { KeyService } from "@bitwarden/key-management"; @Pipe({ name: "fingerprint", + standalone: false, }) export class FingerprintPipe { constructor(private keyService: KeyService) {} diff --git a/libs/angular/src/platform/pipes/i18n.pipe.ts b/libs/angular/src/platform/pipes/i18n.pipe.ts index a6fdbc78255..a1c6122ba16 100644 --- a/libs/angular/src/platform/pipes/i18n.pipe.ts +++ b/libs/angular/src/platform/pipes/i18n.pipe.ts @@ -7,6 +7,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic */ @Pipe({ name: "i18n", + standalone: false, }) export class I18nPipe implements PipeTransform { constructor(private i18nService: I18nService) {} diff --git a/libs/angular/src/tools/password-strength/password-strength.component.ts b/libs/angular/src/tools/password-strength/password-strength.component.ts index d23225b7c0c..ca9892d9c6c 100644 --- a/libs/angular/src/tools/password-strength/password-strength.component.ts +++ b/libs/angular/src/tools/password-strength/password-strength.component.ts @@ -16,6 +16,7 @@ export interface PasswordColorText { @Component({ selector: "app-password-strength", templateUrl: "password-strength.component.html", + standalone: false, }) export class PasswordStrengthComponent implements OnChanges { @Input() showText = false; diff --git a/libs/angular/src/vault/components/icon.component.ts b/libs/angular/src/vault/components/icon.component.ts index 248378bf5ee..fd178db23b6 100644 --- a/libs/angular/src/vault/components/icon.component.ts +++ b/libs/angular/src/vault/components/icon.component.ts @@ -19,6 +19,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; selector: "app-vault-icon", templateUrl: "icon.component.html", changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, }) export class IconComponent { /** diff --git a/libs/components/src/app/app.component.ts b/libs/components/src/app/app.component.ts index d4c9ddd2f41..ed8cf595d5e 100644 --- a/libs/components/src/app/app.component.ts +++ b/libs/components/src/app/app.component.ts @@ -3,6 +3,7 @@ import { Component } from "@angular/core"; @Component({ selector: "app-root", template: "", + standalone: false, }) export class AppComponent { title = "components"; diff --git a/libs/components/src/input/autofocus.directive.ts b/libs/components/src/input/autofocus.directive.ts index 46eb1b15b16..3fd06156f39 100644 --- a/libs/components/src/input/autofocus.directive.ts +++ b/libs/components/src/input/autofocus.directive.ts @@ -19,6 +19,7 @@ import { FocusableElement } from "../shared/focusable-element"; */ @Directive({ selector: "[appAutofocus], [bitAutofocus]", + standalone: false, }) export class AutofocusDirective implements AfterContentChecked { @Input() set appAutofocus(condition: boolean | string) { diff --git a/libs/tools/generator/components/src/catchall-settings.component.ts b/libs/tools/generator/components/src/catchall-settings.component.ts index 92d0909e661..3bf586c5a29 100644 --- a/libs/tools/generator/components/src/catchall-settings.component.ts +++ b/libs/tools/generator/components/src/catchall-settings.component.ts @@ -24,6 +24,7 @@ import { @Component({ selector: "tools-catchall-settings", templateUrl: "catchall-settings.component.html", + standalone: false, }) export class CatchallSettingsComponent implements OnInit, OnDestroy, OnChanges { /** Instantiates the component diff --git a/libs/tools/generator/components/src/credential-generator.component.ts b/libs/tools/generator/components/src/credential-generator.component.ts index 4a83f2a9a92..0b48b4cf0f7 100644 --- a/libs/tools/generator/components/src/credential-generator.component.ts +++ b/libs/tools/generator/components/src/credential-generator.component.ts @@ -67,6 +67,7 @@ const NONE_SELECTED = "none"; @Component({ selector: "tools-credential-generator", templateUrl: "credential-generator.component.html", + standalone: false, }) export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestroy { private readonly destroyed = new Subject(); diff --git a/libs/tools/generator/components/src/forwarder-settings.component.ts b/libs/tools/generator/components/src/forwarder-settings.component.ts index 8a5311fb7f3..689cc7e258c 100644 --- a/libs/tools/generator/components/src/forwarder-settings.component.ts +++ b/libs/tools/generator/components/src/forwarder-settings.component.ts @@ -33,6 +33,7 @@ const Controls = Object.freeze({ @Component({ selector: "tools-forwarder-settings", templateUrl: "forwarder-settings.component.html", + standalone: false, }) export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/passphrase-settings.component.ts b/libs/tools/generator/components/src/passphrase-settings.component.ts index 0509ceab60f..405914977c5 100644 --- a/libs/tools/generator/components/src/passphrase-settings.component.ts +++ b/libs/tools/generator/components/src/passphrase-settings.component.ts @@ -33,6 +33,7 @@ const Controls = Object.freeze({ @Component({ selector: "tools-passphrase-settings", templateUrl: "passphrase-settings.component.html", + standalone: false, }) export class PassphraseSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/password-generator.component.ts b/libs/tools/generator/components/src/password-generator.component.ts index e4e173829a6..9643c857473 100644 --- a/libs/tools/generator/components/src/password-generator.component.ts +++ b/libs/tools/generator/components/src/password-generator.component.ts @@ -53,6 +53,7 @@ import { GeneratorHistoryService } from "@bitwarden/generator-history"; @Component({ selector: "tools-password-generator", templateUrl: "password-generator.component.html", + standalone: false, }) export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy { constructor( diff --git a/libs/tools/generator/components/src/password-settings.component.ts b/libs/tools/generator/components/src/password-settings.component.ts index e4005c6fda7..346e9549cd8 100644 --- a/libs/tools/generator/components/src/password-settings.component.ts +++ b/libs/tools/generator/components/src/password-settings.component.ts @@ -37,6 +37,7 @@ const Controls = Object.freeze({ @Component({ selector: "tools-password-settings", templateUrl: "password-settings.component.html", + standalone: false, }) export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/subaddress-settings.component.ts b/libs/tools/generator/components/src/subaddress-settings.component.ts index 8bde260693c..b09ecc86f9e 100644 --- a/libs/tools/generator/components/src/subaddress-settings.component.ts +++ b/libs/tools/generator/components/src/subaddress-settings.component.ts @@ -24,6 +24,7 @@ import { @Component({ selector: "tools-subaddress-settings", templateUrl: "subaddress-settings.component.html", + standalone: false, }) export class SubaddressSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/username-generator.component.ts b/libs/tools/generator/components/src/username-generator.component.ts index 0c06b89adb4..de48a9bd6b1 100644 --- a/libs/tools/generator/components/src/username-generator.component.ts +++ b/libs/tools/generator/components/src/username-generator.component.ts @@ -67,6 +67,7 @@ const NONE_SELECTED = "none"; @Component({ selector: "tools-username-generator", templateUrl: "username-generator.component.html", + standalone: false, }) export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the username generator diff --git a/libs/tools/generator/components/src/username-settings.component.ts b/libs/tools/generator/components/src/username-settings.component.ts index 7e59ef9c379..ea3cfbd35fb 100644 --- a/libs/tools/generator/components/src/username-settings.component.ts +++ b/libs/tools/generator/components/src/username-settings.component.ts @@ -24,6 +24,7 @@ import { @Component({ selector: "tools-username-settings", templateUrl: "username-settings.component.html", + standalone: false, }) export class UsernameSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component From b30faeb62ba2c257f691454960b082f799618651 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Thu, 15 May 2025 10:09:51 -0700 Subject: [PATCH 067/245] [PM-21554] - Creating a new item while editing edits the item (#14770) * fix adding new cipher while editing a cipher * don't set updatedCipherView if the cached cipher has an id and the new one doesn't * fix cipher form config --- apps/desktop/src/vault/app/vault/vault-v2.component.ts | 1 + .../src/cipher-form/components/cipher-form.component.ts | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index 66e77580d1c..6c60aaf0f02 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -538,6 +538,7 @@ export class VaultV2Component implements OnInit, OnDestroy { } this.addType = type || this.activeFilter.cipherType; this.cipher = new CipherView(); + this.cipherId = null; await this.buildFormConfig("add"); this.action = "add"; this.prefillCipherFromFilter(); diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.ts b/libs/vault/src/cipher-form/components/cipher-form.component.ts index 8b99b60bc16..eebfca65f36 100644 --- a/libs/vault/src/cipher-form/components/cipher-form.component.ts +++ b/libs/vault/src/cipher-form/components/cipher-form.component.ts @@ -275,11 +275,6 @@ export class CipherFormComponent implements AfterViewInit, OnInit, OnChanges, Ci if (this.updatedCipherView.id === cachedCipher.id) { this.updatedCipherView = cachedCipher; } - - // `id` is null when a cipher is being added - if (this.updatedCipherView.id === null) { - this.updatedCipherView = cachedCipher; - } } constructor( From 82d0925f4eb2727b8c635d6aeef0355fb8e8c602 Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Thu, 15 May 2025 13:32:05 -0400 Subject: [PATCH 068/245] PM-21620 finalize a11y UX concerns for option selection (#14777) * PM-21620 finalize a11y UX concerns for option selection * SR should announce that the button has a menu popup collapsed and expanded when they open it * support up and down keys -close menu when other menu expanded * dynamic aria label * type safety * instanceOf to replace as Node for type * default aria hidden prop that can be overridden * update mock and make message more descriptive * Update apps/browser/src/autofill/content/components/icons/collection-shared.ts Co-authored-by: Jonathan Prusik --------- Co-authored-by: Jonathan Prusik --- apps/browser/src/_locales/en/messages.json | 17 ++++++-- .../buttons/option-selection-button.ts | 3 ++ .../content/components/common-types.ts | 1 + .../content/components/icons/angle-down.ts | 9 +++- .../content/components/icons/angle-up.ts | 9 +++- .../content/components/icons/business.ts | 9 +++- .../content/components/icons/close.ts | 9 +++- .../components/icons/collection-shared.ts | 9 +++- .../components/icons/exclamation-triangle.ts | 9 +++- .../content/components/icons/external-link.ts | 9 +++- .../content/components/icons/family.ts | 9 +++- .../content/components/icons/folder.ts | 9 +++- .../content/components/icons/globe.ts | 9 +++- .../content/components/icons/pencil-square.ts | 9 +++- .../content/components/icons/shield.ts | 9 +++- .../autofill/content/components/icons/user.ts | 9 +++- .../components/lit-stories/mock-data.ts | 1 + .../option-selection/option-item.ts | 16 +++++++- .../option-selection/option-items.ts | 28 ++++++++++++- .../option-selection/option-selection.ts | 41 ++++++++++++++++--- 20 files changed, 187 insertions(+), 37 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index df35facff3c..a14d769d4cd 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1090,13 +1090,24 @@ }, "notificationLoginSaveConfirmation": { "message": "saved to Bitwarden.", - "description": "Shown to user after item is saved." }, "notificationLoginUpdatedConfirmation": { "message": "updated in Bitwarden.", "description": "Shown to user after item is updated." }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } + }, "saveAsNewLoginAction": { "message": "Save as new login", "description": "Button text for saving login details as a new entry." @@ -3585,7 +3596,7 @@ "orgTrustWarning1": { "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." }, - "trustUser":{ + "trustUser": { "message": "Trust user" }, "sendsNoItemsTitle": { @@ -5325,4 +5336,4 @@ "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } -} +} \ No newline at end of file diff --git a/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts b/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts index e3c7e0d54e6..3912c791d34 100644 --- a/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts @@ -33,6 +33,9 @@ export function OptionSelectionButton({ class=${selectionButtonStyles({ disabled, toggledOn, theme })} title=${text} type="button" + aria-haspopup="menu" + aria-expanded=${toggledOn} + aria-controls="option-menu" @click=${handleButtonClick} > ${buttonIcon ?? nothing} diff --git a/apps/browser/src/autofill/content/components/common-types.ts b/apps/browser/src/autofill/content/components/common-types.ts index 740b6963b16..5967f6205a9 100644 --- a/apps/browser/src/autofill/content/components/common-types.ts +++ b/apps/browser/src/autofill/content/components/common-types.ts @@ -11,6 +11,7 @@ export type IconProps = { color?: string; disabled?: boolean; theme: Theme; + ariaHidden?: boolean; }; export type Option = { diff --git a/apps/browser/src/autofill/content/components/icons/angle-down.ts b/apps/browser/src/autofill/content/components/icons/angle-down.ts index 27cd5ab81c5..2bd54f7dbee 100644 --- a/apps/browser/src/autofill/content/components/icons/angle-down.ts +++ b/apps/browser/src/autofill/content/components/icons/angle-down.ts @@ -4,11 +4,16 @@ import { html } from "lit"; import { IconProps } from "../common-types"; import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; -export function AngleDown({ color, disabled, theme }: IconProps) { +export function AngleDown({ ariaHidden = true, color, disabled, theme }: IconProps) { const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; return html` - + + + + + + + + + +
diff --git a/apps/browser/src/autofill/content/components/option-selection/option-items.ts b/apps/browser/src/autofill/content/components/option-selection/option-items.ts index bb7f5afd0fd..854b7c05df1 100644 --- a/apps/browser/src/autofill/content/components/option-selection/option-items.ts +++ b/apps/browser/src/autofill/content/components/option-selection/option-items.ts @@ -33,17 +33,41 @@ export function OptionItems({ const isSafari = false; return html` -
+
handleMenuKeyUp(e)} + > ${label ? html`
${label}
` : nothing}
${options.map((option) => - OptionItem({ ...option, theme, handleSelection: () => handleOptionSelection(option) }), + OptionItem({ + ...option, + theme, + contextLabel: label, + handleSelection: () => handleOptionSelection(option), + }), )}
`; } +function handleMenuKeyUp(event: KeyboardEvent) { + const items = [ + ...(event.currentTarget as HTMLElement).querySelectorAll('[tabindex="0"]'), + ]; + const index = items.indexOf(document.activeElement as HTMLElement); + const direction = event.key === "ArrowDown" ? 1 : event.key === "ArrowUp" ? -1 : 0; + + if (index === -1 || direction === 0) { + return; + } + + event.preventDefault(); + items[(index + direction + items.length) % items.length]?.focus(); +} + const optionsStyles = ({ theme, topOffset }: { theme: Theme; topOffset: number }) => css` ${typography.body1} diff --git a/apps/browser/src/autofill/content/components/option-selection/option-selection.ts b/apps/browser/src/autofill/content/components/option-selection/option-selection.ts index 49b51852a39..c7dceb2b5b4 100644 --- a/apps/browser/src/autofill/content/components/option-selection/option-selection.ts +++ b/apps/browser/src/autofill/content/components/option-selection/option-selection.ts @@ -48,10 +48,18 @@ export class OptionSelection extends LitElement { @state() private selection?: Option; - private handleButtonClick = (event: Event) => { + private static currentOpenInstance: OptionSelection | null = null; + + private handleButtonClick = async (event: Event) => { if (!this.disabled) { - // Menu is about to be shown - if (!this.showMenu) { + const isOpening = !this.showMenu; + + if (isOpening) { + if (OptionSelection.currentOpenInstance && OptionSelection.currentOpenInstance !== this) { + OptionSelection.currentOpenInstance.showMenu = false; + } + OptionSelection.currentOpenInstance = this; + this.menuTopOffset = this.offsetTop; // Distance from right edge of button to left edge of the viewport @@ -71,9 +79,29 @@ export class OptionSelection extends LitElement { optionsMenuItemMaxWidth + optionItemIconWidth + 2 + 8 + 12 * 2; this.menuIsEndJustified = distanceFromViewportRightEdge < maxDifferenceThreshold; + } else { + if (OptionSelection.currentOpenInstance === this) { + OptionSelection.currentOpenInstance = null; + } } - this.showMenu = !this.showMenu; + this.showMenu = isOpening; + + if (this.showMenu) { + await this.updateComplete; + const firstItem = this.querySelector('#option-menu [tabindex="0"]') as HTMLElement; + firstItem?.focus(); + } + } + }; + + private handleFocusOut = (event: FocusEvent) => { + const relatedTarget = event.relatedTarget; + if (!(relatedTarget instanceof Node) || !this.contains(relatedTarget)) { + this.showMenu = false; + if (OptionSelection.currentOpenInstance === this) { + OptionSelection.currentOpenInstance = null; + } } }; @@ -95,7 +123,10 @@ export class OptionSelection extends LitElement { } return html` -
+
${OptionSelectionButton({ disabled: this.disabled, icon: this.selection?.icon, From ee4c3cfd9475b57118f06eb5dedc82707af92e26 Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Thu, 15 May 2025 15:10:38 -0400 Subject: [PATCH 069/245] [PM-21663] nudge service name refactor (#14789) * update names of vault nudge service and their corresponding files, convert components using showNudge$ to instead target spotlight and badges directly with new observables. Core logic for dismiss remains the same --- .../popup/settings/autofill.component.ts | 20 ++--- apps/browser/src/popup/tabs-v2.component.ts | 6 +- .../popup/settings/settings-v2.component.html | 6 +- .../popup/settings/settings-v2.component.ts | 26 +++--- .../vault-v2/vault-v2.component.html | 4 +- .../components/vault-v2/vault-v2.component.ts | 18 ++-- .../settings/download-bitwarden.component.ts | 6 +- .../src/platform/state/state-definitions.ts | 2 +- .../src/cipher-form/cipher-form.stories.ts | 4 +- .../new-item-nudge.component.spec.ts | 23 +++-- .../new-item-nudge.component.ts | 28 +++--- libs/vault/src/index.ts | 2 +- .../autofill-nudge.service.ts | 6 +- .../download-bitwarden-nudge.service.ts | 4 +- .../empty-vault-nudge.service.ts | 4 +- .../has-items-nudge.service.ts | 6 +- .../new-item-nudge.service.ts | 14 +-- .../services/default-single-nudge.service.ts | 24 ++---- ...service.spec.ts => nudges.service.spec.ts} | 62 +++++++++---- ...lt-nudges.service.ts => nudges.service.ts} | 86 +++++++++++++------ 20 files changed, 199 insertions(+), 152 deletions(-) rename libs/vault/src/services/{vault-nudges.service.spec.ts => nudges.service.spec.ts} (72%) rename libs/vault/src/services/{vault-nudges.service.ts => nudges.service.ts} (56%) diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.ts b/apps/browser/src/autofill/popup/settings/autofill.component.ts index d63f9a4589d..2d29067cf0f 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill.component.ts @@ -4,14 +4,14 @@ import { CommonModule } from "@angular/common"; import { Component, DestroyRef, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { + FormBuilder, + FormControl, + FormGroup, FormsModule, ReactiveFormsModule, - FormBuilder, - FormGroup, - FormControl, } from "@angular/forms"; import { RouterModule } from "@angular/router"; -import { Observable, filter, firstValueFrom, map, switchMap } from "rxjs"; +import { filter, firstValueFrom, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -55,7 +55,7 @@ import { SelectModule, TypographyModule, } from "@bitwarden/components"; -import { SpotlightComponent, VaultNudgesService, VaultNudgeType } from "@bitwarden/vault"; +import { NudgesService, NudgeType, SpotlightComponent } from "@bitwarden/vault"; import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; @@ -108,9 +108,7 @@ export class AutofillComponent implements OnInit { protected showSpotlightNudge$: Observable = this.accountService.activeAccount$.pipe( filter((account): account is Account => account !== null), switchMap((account) => - this.vaultNudgesService - .showNudge$(VaultNudgeType.AutofillNudge, account.id) - .pipe(map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed)), + this.nudgesService.showNudgeSpotlight$(NudgeType.AutofillNudge, account.id), ), ); @@ -155,7 +153,7 @@ export class AutofillComponent implements OnInit { private configService: ConfigService, private formBuilder: FormBuilder, private destroyRef: DestroyRef, - private vaultNudgesService: VaultNudgesService, + private nudgesService: NudgesService, private accountService: AccountService, private autofillBrowserSettingsService: AutofillBrowserSettingsService, ) { @@ -343,8 +341,8 @@ export class AutofillComponent implements OnInit { } async dismissSpotlight() { - await this.vaultNudgesService.dismissNudge( - VaultNudgeType.AutofillNudge, + await this.nudgesService.dismissNudge( + NudgeType.AutofillNudge, await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)), ); } diff --git a/apps/browser/src/popup/tabs-v2.component.ts b/apps/browser/src/popup/tabs-v2.component.ts index 5df8fb85d6d..0ca763d510d 100644 --- a/apps/browser/src/popup/tabs-v2.component.ts +++ b/apps/browser/src/popup/tabs-v2.component.ts @@ -6,7 +6,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { Icons } from "@bitwarden/components"; -import { VaultNudgesService } from "@bitwarden/vault"; +import { NudgesService } from "@bitwarden/vault"; import { NavButton } from "../platform/popup/layout/popup-tab-navigation.component"; @@ -18,7 +18,7 @@ import { NavButton } from "../platform/popup/layout/popup-tab-navigation.compone export class TabsV2Component { private hasActiveBadges$ = this.accountService.activeAccount$ .pipe(getUserId) - .pipe(switchMap((userId) => this.vaultNudgesService.hasActiveBadges$(userId))); + .pipe(switchMap((userId) => this.nudgesService.hasActiveBadges$(userId))); protected navButtons$: Observable = combineLatest([ this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge), this.hasActiveBadges$, @@ -54,7 +54,7 @@ export class TabsV2Component { }), ); constructor( - private vaultNudgesService: VaultNudgesService, + private nudgesService: NudgesService, private accountService: AccountService, private readonly configService: ConfigService, ) {} diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.html b/apps/browser/src/tools/popup/settings/settings-v2.component.html index 8d31ccf8371..dc53f95a7cf 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.html +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.html @@ -41,7 +41,7 @@
@@ -51,7 +51,7 @@ Will make this dynamic when more nudges are added -->

{{ "downloadBitwardenOnAllDevices" | i18n }}

= this.authenticatedAccount$.pipe( + downloadBitwardenNudgeStatus$: Observable = this.authenticatedAccount$.pipe( switchMap((account) => - this.vaultNudgesService.showNudge$(VaultNudgeType.DownloadBitwarden, account.id), + this.nudgesService.showNudgeBadge$(NudgeType.DownloadBitwarden, account.id), ), ); - showVaultBadge$: Observable = this.authenticatedAccount$.pipe( + showVaultBadge$: Observable = this.authenticatedAccount$.pipe( switchMap((account) => - this.vaultNudgesService.showNudge$(VaultNudgeType.EmptyVaultNudge, account.id), + this.nudgesService.showNudgeBadge$(NudgeType.EmptyVaultNudge, account.id), ), ); @@ -68,9 +68,9 @@ export class SettingsV2Component implements OnInit { this.authenticatedAccount$, ]).pipe( switchMap(([defaultBrowserAutofillDisabled, account]) => - this.vaultNudgesService.showNudge$(VaultNudgeType.AutofillNudge, account.id).pipe( - map((nudgeStatus) => { - return !defaultBrowserAutofillDisabled && nudgeStatus.hasBadgeDismissed === false; + this.nudgesService.showNudgeBadge$(NudgeType.AutofillNudge, account.id).pipe( + map((badgeStatus) => { + return !defaultBrowserAutofillDisabled && badgeStatus; }), ), ), @@ -81,7 +81,7 @@ export class SettingsV2Component implements OnInit { ); constructor( - private readonly vaultNudgesService: VaultNudgesService, + private readonly nudgesService: NudgesService, private readonly accountService: AccountService, private readonly autofillBrowserSettingsService: AutofillBrowserSettingsService, private readonly configService: ConfigService, @@ -94,10 +94,10 @@ export class SettingsV2Component implements OnInit { ); } - async dismissBadge(type: VaultNudgeType) { - if (!(await firstValueFrom(this.showVaultBadge$)).hasBadgeDismissed) { + async dismissBadge(type: NudgeType) { + if (await firstValueFrom(this.showVaultBadge$)) { const account = await firstValueFrom(this.authenticatedAccount$); - await this.vaultNudgesService.dismissNudge(type, account.id as UserId, true); + await this.nudgesService.dismissNudge(type, account.id as UserId, true); } } } diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index 43a96fc616e..42e772be062 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -36,7 +36,7 @@ [subtitle]="'emptyVaultNudgeBody' | i18n" [buttonText]="'emptyVaultNudgeButton' | i18n" (onButtonClick)="navigateToImport()" - (onDismiss)="dismissVaultNudgeSpotlight(VaultNudgeType.EmptyVaultNudge)" + (onDismiss)="dismissVaultNudgeSpotlight(NudgeType.EmptyVaultNudge)" > @@ -44,7 +44,7 @@
- @let showFooterBorder = !isDrawer || background === "alt" || bodyHasScrolledFrom().bottom;
diff --git a/libs/components/src/dialog/dialog/dialog.component.ts b/libs/components/src/dialog/dialog/dialog.component.ts index 8cf9cd18c78..504dbd3a1ea 100644 --- a/libs/components/src/dialog/dialog/dialog.component.ts +++ b/libs/components/src/dialog/dialog/dialog.component.ts @@ -1,18 +1,14 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { CdkTrapFocus } from "@angular/cdk/a11y"; import { coerceBooleanProperty } from "@angular/cdk/coercion"; -import { CdkScrollable } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; -import { Component, HostBinding, Input, inject, viewChild } from "@angular/core"; +import { Component, HostBinding, Input } from "@angular/core"; import { I18nPipe } from "@bitwarden/ui-common"; import { BitIconButtonComponent } from "../../icon-button/icon-button.component"; import { TypographyDirective } from "../../typography/typography.directive"; -import { hasScrolledFrom } from "../../utils/has-scrolled-from"; import { fadeIn } from "../animations"; -import { DialogRef } from "../dialog.service"; import { DialogCloseDirective } from "../directives/dialog-close.directive"; import { DialogTitleContainerDirective } from "../directives/dialog-title-container.directive"; @@ -21,9 +17,6 @@ import { DialogTitleContainerDirective } from "../directives/dialog-title-contai templateUrl: "./dialog.component.html", animations: [fadeIn], standalone: true, - host: { - "(keydown.esc)": "handleEsc($event)", - }, imports: [ CommonModule, DialogTitleContainerDirective, @@ -31,15 +24,9 @@ import { DialogTitleContainerDirective } from "../directives/dialog-title-contai BitIconButtonComponent, DialogCloseDirective, I18nPipe, - CdkTrapFocus, - CdkScrollable, ], }) export class DialogComponent { - protected dialogRef = inject(DialogRef, { optional: true }); - private scrollableBody = viewChild.required(CdkScrollable); - protected bodyHasScrolledFrom = hasScrolledFrom(this.scrollableBody); - /** Background color */ @Input() background: "default" | "alt" = "default"; @@ -77,31 +64,21 @@ export class DialogComponent { @HostBinding("class") get classes() { // `tw-max-h-[90vh]` is needed to prevent dialogs from overlapping the desktop header - return ["tw-flex", "tw-flex-col", "tw-w-screen"] - .concat( - this.width, - this.dialogRef?.isDrawer - ? ["tw-min-h-screen", "md:tw-w-[23rem]"] - : ["tw-p-4", "tw-w-screen", "tw-max-h-[90vh]"], - ) - .flat(); - } - - handleEsc(event: Event) { - this.dialogRef?.close(); - event.stopPropagation(); + return ["tw-flex", "tw-flex-col", "tw-w-screen", "tw-p-4", "tw-max-h-[90vh]"].concat( + this.width, + ); } get width() { switch (this.dialogSize) { case "small": { - return "md:tw-max-w-sm"; + return "tw-max-w-sm"; } case "large": { - return "md:tw-max-w-3xl"; + return "tw-max-w-3xl"; } default: { - return "md:tw-max-w-xl"; + return "tw-max-w-xl"; } } } diff --git a/libs/components/src/dialog/dialogs.mdx b/libs/components/src/dialog/dialogs.mdx index 3f44f31a5eb..63df0bfc131 100644 --- a/libs/components/src/dialog/dialogs.mdx +++ b/libs/components/src/dialog/dialogs.mdx @@ -22,9 +22,6 @@ For alerts or simple confirmation actions, like speedbumps, use the Dialogs's should be used sparingly as they do call extra attention to themselves and can be interruptive if overused. -For non-blocking, supplementary content, open dialogs as a -[Drawer](?path=/story/component-library-dialogs-service--drawer) (requires `bit-layout`). - ## Placement Dialogs should be centered vertically and horizontally on screen. Dialogs height should expand to diff --git a/libs/components/src/dialog/index.ts b/libs/components/src/dialog/index.ts index fb4c2721b81..0ab9a5d9e67 100644 --- a/libs/components/src/dialog/index.ts +++ b/libs/components/src/dialog/index.ts @@ -1,4 +1,4 @@ export * from "./dialog.module"; export * from "./simple-dialog/types"; export * from "./dialog.service"; -export { DIALOG_DATA } from "@angular/cdk/dialog"; +export { DialogConfig, DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; diff --git a/libs/components/src/drawer/drawer-body.component.ts b/libs/components/src/drawer/drawer-body.component.ts index 563987dd8b0..9bd2adcffbc 100644 --- a/libs/components/src/drawer/drawer-body.component.ts +++ b/libs/components/src/drawer/drawer-body.component.ts @@ -1,7 +1,7 @@ import { CdkScrollable } from "@angular/cdk/scrolling"; -import { ChangeDetectionStrategy, Component } from "@angular/core"; - -import { hasScrolledFrom } from "../utils/has-scrolled-from"; +import { ChangeDetectionStrategy, Component, Signal, inject } from "@angular/core"; +import { toSignal } from "@angular/core/rxjs-interop"; +import { map } from "rxjs"; /** * Body container for `bit-drawer` @@ -14,7 +14,7 @@ import { hasScrolledFrom } from "../utils/has-scrolled-from"; host: { class: "tw-p-4 tw-pt-0 tw-block tw-overflow-auto tw-border-solid tw-border tw-border-transparent tw-transition-colors tw-duration-200", - "[class.tw-border-t-secondary-300]": "this.hasScrolledFrom().top", + "[class.tw-border-t-secondary-300]": "isScrolled()", }, hostDirectives: [ { @@ -24,5 +24,13 @@ import { hasScrolledFrom } from "../utils/has-scrolled-from"; template: ` `, }) export class DrawerBodyComponent { - protected hasScrolledFrom = hasScrolledFrom(); + private scrollable = inject(CdkScrollable); + + /** TODO: share this utility with browser popup header? */ + protected isScrolled: Signal = toSignal( + this.scrollable + .elementScrolled() + .pipe(map(() => this.scrollable.measureScrollOffset("top") > 0)), + { initialValue: false }, + ); } diff --git a/libs/components/src/drawer/drawer.component.ts b/libs/components/src/drawer/drawer.component.ts index 351be57e07b..ccabb6f0b6e 100644 --- a/libs/components/src/drawer/drawer.component.ts +++ b/libs/components/src/drawer/drawer.component.ts @@ -10,7 +10,7 @@ import { viewChild, } from "@angular/core"; -import { DrawerService } from "./drawer.service"; +import { DrawerHostDirective } from "./drawer-host.directive"; /** * A drawer is a panel of supplementary content that is adjacent to the page's main content. @@ -25,7 +25,7 @@ import { DrawerService } from "./drawer.service"; templateUrl: "drawer.component.html", }) export class DrawerComponent { - private drawerHost = inject(DrawerService); + private drawerHost = inject(DrawerHostDirective); private portal = viewChild.required(CdkPortal); /** diff --git a/libs/components/src/drawer/drawer.mdx b/libs/components/src/drawer/drawer.mdx index bc99fa290d6..57d618cfe95 100644 --- a/libs/components/src/drawer/drawer.mdx +++ b/libs/components/src/drawer/drawer.mdx @@ -12,8 +12,6 @@ import { DrawerComponent } from "@bitwarden/components"; # Drawer -**Note: `bit-drawer` is deprecated. Use `bit-dialog` and `DialogService.openDrawer(...)` instead.** - A drawer is a panel of supplementary content that is adjacent to the page's main content. diff --git a/libs/components/src/drawer/drawer.service.ts b/libs/components/src/drawer/drawer.service.ts deleted file mode 100644 index dd8575efee8..00000000000 --- a/libs/components/src/drawer/drawer.service.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Portal } from "@angular/cdk/portal"; -import { Injectable, signal } from "@angular/core"; - -@Injectable({ providedIn: "root" }) -export class DrawerService { - private _portal = signal | undefined>(undefined); - - /** The portal to display */ - portal = this._portal.asReadonly(); - - open(portal: Portal) { - this._portal.set(portal); - } - - close(portal: Portal) { - if (portal === this.portal()) { - this._portal.set(undefined); - } - } -} diff --git a/libs/components/src/layout/index.ts b/libs/components/src/layout/index.ts index a257a4dde85..6994a4f639f 100644 --- a/libs/components/src/layout/index.ts +++ b/libs/components/src/layout/index.ts @@ -1,2 +1 @@ export * from "./layout.component"; -export * from "./scroll-layout.directive"; diff --git a/libs/components/src/layout/layout.component.html b/libs/components/src/layout/layout.component.html index 19280c99756..33b8de81572 100644 --- a/libs/components/src/layout/layout.component.html +++ b/libs/components/src/layout/layout.component.html @@ -1,52 +1,43 @@ -@let mainContentId = "main-content"; -
-
- - -
- - - - @if ( - { - open: sideNavService.open$ | async, - }; - as data - ) { -
- @if (data.open) { -
- } -
- } -
-
-
- -
+ +
+
+ +
+ + + + @if ( + { + open: sideNavService.open$ | async, + }; + as data + ) { +
+ @if (data.open) { +
+ } +
+ } +
+
diff --git a/libs/components/src/layout/layout.component.ts b/libs/components/src/layout/layout.component.ts index 6ee7dd8222e..7bf8a6ad173 100644 --- a/libs/components/src/layout/layout.component.ts +++ b/libs/components/src/layout/layout.component.ts @@ -1,10 +1,9 @@ -import { A11yModule, CdkTrapFocus } from "@angular/cdk/a11y"; import { PortalModule } from "@angular/cdk/portal"; import { CommonModule } from "@angular/common"; -import { Component, ElementRef, inject, viewChild } from "@angular/core"; +import { Component, inject } from "@angular/core"; import { RouterModule } from "@angular/router"; -import { DrawerService } from "../drawer/drawer.service"; +import { DrawerHostDirective } from "../drawer/drawer-host.directive"; import { LinkModule } from "../link"; import { SideNavService } from "../navigation/side-nav.service"; import { SharedModule } from "../shared"; @@ -13,23 +12,16 @@ import { SharedModule } from "../shared"; selector: "bit-layout", templateUrl: "layout.component.html", standalone: true, - imports: [ - CommonModule, - SharedModule, - LinkModule, - RouterModule, - PortalModule, - A11yModule, - CdkTrapFocus, - ], + imports: [CommonModule, SharedModule, LinkModule, RouterModule, PortalModule], + hostDirectives: [DrawerHostDirective], }) export class LayoutComponent { + protected mainContentId = "main-content"; + protected sideNavService = inject(SideNavService); - protected drawerPortal = inject(DrawerService).portal; + protected drawerPortal = inject(DrawerHostDirective).portal; - private mainContent = viewChild.required>("main"); - - protected focusMainContent() { - this.mainContent().nativeElement.focus(); + focusMainContent() { + document.getElementById(this.mainContentId)?.focus(); } } diff --git a/libs/components/src/layout/scroll-layout.directive.ts b/libs/components/src/layout/scroll-layout.directive.ts deleted file mode 100644 index d3ea2bf557b..00000000000 --- a/libs/components/src/layout/scroll-layout.directive.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Directionality } from "@angular/cdk/bidi"; -import { CdkVirtualScrollable, ScrollDispatcher, VIRTUAL_SCROLLABLE } from "@angular/cdk/scrolling"; -import { Directive, ElementRef, NgZone, Optional } from "@angular/core"; - -@Directive({ - selector: "cdk-virtual-scroll-viewport[bitScrollLayout]", - standalone: true, - providers: [{ provide: VIRTUAL_SCROLLABLE, useExisting: ScrollLayoutDirective }], -}) -export class ScrollLayoutDirective extends CdkVirtualScrollable { - private mainRef: ElementRef; - - constructor(scrollDispatcher: ScrollDispatcher, ngZone: NgZone, @Optional() dir: Directionality) { - const mainEl = document.querySelector("main")!; - if (!mainEl) { - // eslint-disable-next-line no-console - console.error("HTML main element must be an ancestor of [bitScrollLayout]"); - } - const mainRef = new ElementRef(mainEl); - super(mainRef, scrollDispatcher, ngZone, dir); - this.mainRef = mainRef; - } - - override getElementRef(): ElementRef { - return this.mainRef; - } - - override measureBoundingClientRectWithScrollOffset( - from: "left" | "top" | "right" | "bottom", - ): number { - return ( - this.mainRef.nativeElement.getBoundingClientRect()[from] - this.measureScrollOffset(from) - ); - } -} diff --git a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts index 904b9e11c3a..7709506f050 100644 --- a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts @@ -3,23 +3,15 @@ import { Component, OnInit } from "@angular/core"; import { DialogModule, DialogService } from "../../../dialog"; import { IconButtonModule } from "../../../icon-button"; -import { ScrollLayoutDirective } from "../../../layout"; import { SectionComponent } from "../../../section"; import { TableDataSource, TableModule } from "../../../table"; @Component({ selector: "dialog-virtual-scroll-block", standalone: true, - imports: [ - DialogModule, - IconButtonModule, - SectionComponent, - TableModule, - ScrollingModule, - ScrollLayoutDirective, - ], + imports: [DialogModule, IconButtonModule, SectionComponent, TableModule, ScrollingModule], template: /*html*/ ` - + diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts index b62e669d369..70f56d2e28d 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts @@ -12,69 +12,8 @@ import { KitchenSinkToggleList } from "./kitchen-sink-toggle-list.component"; standalone: true, imports: [KitchenSinkSharedModule], template: ` - - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

- - What did foo say to bar? - - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-
+ + Dialog body text goes here. @@ -151,6 +90,72 @@ class KitchenSinkDialog {
+ + + + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. +

+ + What did foo say to bar? + + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. +

+
+
`, }) export class KitchenSinkMainComponent { @@ -163,7 +168,7 @@ export class KitchenSinkMainComponent { } openDrawer() { - this.dialogService.openDrawer(KitchenSinkDialog); + this.drawerOpen.set(true); } navItems = [ diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts index d318e1b5f0e..f57a9de4e68 100644 --- a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts +++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts @@ -14,6 +14,7 @@ import { import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DialogService } from "../../dialog"; import { LayoutComponent } from "../../layout"; import { I18nMockService } from "../../utils/i18n-mock.service"; import { positionFixedWrapperDecorator } from "../storybook-decorators"; @@ -38,20 +39,8 @@ export default { KitchenSinkTable, KitchenSinkToggleList, ], - }), - applicationConfig({ providers: [ - provideNoopAnimations(), - importProvidersFrom( - RouterModule.forRoot( - [ - { path: "", redirectTo: "bitwarden", pathMatch: "full" }, - { path: "bitwarden", component: KitchenSinkMainComponent }, - { path: "virtual-scroll", component: DialogVirtualScrollBlockComponent }, - ], - { useHash: true }, - ), - ), + DialogService, { provide: I18nService, useFactory: () => { @@ -69,6 +58,21 @@ export default { }, ], }), + applicationConfig({ + providers: [ + provideNoopAnimations(), + importProvidersFrom( + RouterModule.forRoot( + [ + { path: "", redirectTo: "bitwarden", pathMatch: "full" }, + { path: "bitwarden", component: KitchenSinkMainComponent }, + { path: "virtual-scroll", component: DialogVirtualScrollBlockComponent }, + ], + { useHash: true }, + ), + ), + ], + }), ], } as Meta; diff --git a/libs/components/src/table/table-scroll.component.html b/libs/components/src/table/table-scroll.component.html index 523912cd7ac..8f2c88ba3ad 100644 --- a/libs/components/src/table/table-scroll.component.html +++ b/libs/components/src/table/table-scroll.component.html @@ -1,5 +1,5 @@ diff --git a/libs/components/src/table/table-scroll.component.ts b/libs/components/src/table/table-scroll.component.ts index e83dbbecc60..9d81e3ffe83 100644 --- a/libs/components/src/table/table-scroll.component.ts +++ b/libs/components/src/table/table-scroll.component.ts @@ -20,8 +20,6 @@ import { TrackByFunction, } from "@angular/core"; -import { ScrollLayoutDirective } from "../layout"; - import { RowDirective } from "./row.directive"; import { TableComponent } from "./table.component"; @@ -58,7 +56,6 @@ export class BitRowDef { CdkFixedSizeVirtualScroll, CdkVirtualForOf, RowDirective, - ScrollLayoutDirective, ], }) export class TableScrollComponent diff --git a/libs/components/src/table/table.mdx b/libs/components/src/table/table.mdx index 59bf5b773a3..8d784190ed9 100644 --- a/libs/components/src/table/table.mdx +++ b/libs/components/src/table/table.mdx @@ -142,7 +142,7 @@ dataSource.filter = (data) => data.orgType === "family"; Rudimentary string filtering is supported out of the box with `TableDataSource.simpleStringFilter`. It works by converting each entry into a string of it's properties. The provided string is then -compared against the filter value using a simple `indexOf` check. For convenience, you can also just +compared against the filter value using a simple `indexOf` check. For convienence, you can also just pass a string directly. ```ts @@ -153,7 +153,7 @@ dataSource.filter = "search value"; ### Virtual Scrolling -It's heavily advised to use virtual scrolling if you expect the table to have any significant amount +It's heavily adviced to use virtual scrolling if you expect the table to have any significant amount of data. This is done by using the `bit-table-scroll` component instead of the `bit-table` component. This component behaves slightly different from the `bit-table` component. Instead of using the `*ngFor` directive to render the rows, you provide a `bitRowDef` template that will be @@ -178,14 +178,6 @@ height and align vertically. ``` -#### Deprecated approach - -Before `bit-table-scroll` was introduced, virtual scroll in tables was implemented manually via -constructs from Angular CDK. This included wrapping the table with a `cdk-virtual-scroll-viewport` -and targeting with `bit-layout`'s scroll container with the `bitScrollLayout` directive. - -This pattern is deprecated in favor of `bit-table-scroll`. - ## Accessibility - Always include a row or column header with your table; this allows assistive technology to better diff --git a/libs/components/src/table/table.stories.ts b/libs/components/src/table/table.stories.ts index d696e6077dd..e8ab24ee8b7 100644 --- a/libs/components/src/table/table.stories.ts +++ b/libs/components/src/table/table.stories.ts @@ -1,13 +1,6 @@ -import { RouterTestingModule } from "@angular/router/testing"; import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; - import { countries } from "../form/countries"; -import { LayoutComponent } from "../layout"; -import { mockLayoutI18n } from "../layout/mocks"; -import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; -import { I18nMockService } from "../utils"; import { TableDataSource } from "./table-data-source"; import { TableModule } from "./table.module"; @@ -15,17 +8,8 @@ import { TableModule } from "./table.module"; export default { title: "Component Library/Table", decorators: [ - positionFixedWrapperDecorator(), moduleMetadata({ - imports: [TableModule, LayoutComponent, RouterTestingModule], - providers: [ - { - provide: I18nService, - useFactory: () => { - return new I18nMockService(mockLayoutI18n); - }, - }, - ], + imports: [TableModule], }), ], argTypes: { @@ -132,20 +116,18 @@ export const Scrollable: Story = { trackBy: (index: number, item: any) => item.id, }, template: ` - - - - Id - Name - Other - - - {{ row.id }} - {{ row.name }} - {{ row.other }} - - - + + + Id + Name + Other + + + {{ row.id }} + {{ row.name }} + {{ row.other }} + + `, }), }; @@ -162,19 +144,17 @@ export const Filterable: Story = { sortFn: (a: any, b: any) => a.id - b.id, }, template: ` - - - - - Name - Value - - - {{ row.name }} - {{ row.value }} - - - + + + + Name + Value + + + {{ row.name }} + {{ row.value }} + + `, }), }; diff --git a/libs/components/src/utils/has-scrolled-from.ts b/libs/components/src/utils/has-scrolled-from.ts deleted file mode 100644 index 44c73465bdd..00000000000 --- a/libs/components/src/utils/has-scrolled-from.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { CdkScrollable } from "@angular/cdk/scrolling"; -import { Signal, inject, signal } from "@angular/core"; -import { toObservable, toSignal } from "@angular/core/rxjs-interop"; -import { map, startWith, switchMap } from "rxjs"; - -export type ScrollState = { - /** `true` when the scrollbar is not at the top-most position */ - top: boolean; - - /** `true` when the scrollbar is not at the bottom-most position */ - bottom: boolean; -}; - -/** - * Check if a `CdkScrollable` instance has been scrolled - * @param scrollable The instance to check, defaults to the one provided by the current injector - * @returns {Signal} - */ -export const hasScrolledFrom = (scrollable?: Signal): Signal => { - const _scrollable = scrollable ?? signal(inject(CdkScrollable)); - const scrollable$ = toObservable(_scrollable); - - const scrollState$ = scrollable$.pipe( - switchMap((_scrollable) => - _scrollable.elementScrolled().pipe( - startWith(null), - map(() => ({ - top: _scrollable.measureScrollOffset("top") > 0, - bottom: _scrollable.measureScrollOffset("bottom") > 0, - })), - ), - ), - ); - - return toSignal(scrollState$, { - initialValue: { - top: false, - bottom: false, - }, - }); -}; From b7bbf99682b58f38810b56f680ec05c26e2596e7 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Fri, 16 May 2025 17:33:33 -0400 Subject: [PATCH 100/245] Handle null or undefined fields (#14832) --- libs/common/src/vault/models/domain/fido2-credential.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/common/src/vault/models/domain/fido2-credential.ts b/libs/common/src/vault/models/domain/fido2-credential.ts index 7002a58150d..e4e874e367b 100644 --- a/libs/common/src/vault/models/domain/fido2-credential.ts +++ b/libs/common/src/vault/models/domain/fido2-credential.ts @@ -164,8 +164,8 @@ export class Fido2Credential extends Domain { keyCurve: this.keyCurve.toJSON(), keyValue: this.keyValue.toJSON(), rpId: this.rpId.toJSON(), - userHandle: this.userHandle.toJSON(), - userName: this.userName.toJSON(), + userHandle: this.userHandle?.toJSON(), + userName: this.userName?.toJSON(), counter: this.counter.toJSON(), rpName: this.rpName?.toJSON(), userDisplayName: this.userDisplayName?.toJSON(), From 0ec7ab47041bbf1406e2b50f3adc6faa876e80c3 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Sat, 17 May 2025 19:04:06 +0000 Subject: [PATCH 101/245] Autosync the updated translations (#14834) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/az/messages.json | 6 +++--- apps/desktop/src/locales/bg/messages.json | 6 +++--- apps/desktop/src/locales/cs/messages.json | 6 +++--- apps/desktop/src/locales/de/messages.json | 6 +++--- apps/desktop/src/locales/hu/messages.json | 6 +++--- apps/desktop/src/locales/lv/messages.json | 6 +++--- apps/desktop/src/locales/ru/messages.json | 6 +++--- apps/desktop/src/locales/sk/messages.json | 6 +++--- apps/desktop/src/locales/tr/messages.json | 6 +++--- apps/desktop/src/locales/zh_CN/messages.json | 6 +++--- 10 files changed, 30 insertions(+), 30 deletions(-) diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 0f856661551..459458bdb2a 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -3707,13 +3707,13 @@ "message": "Daşı" }, "newFolder": { - "message": "New folder" + "message": "Yeni qovluq" }, "folderName": { - "message": "Folder Name" + "message": "Qovluq adı" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Ana qovluğun adından sonra \"/\" əlavə edərək qovluğu ardıcıl yerləşdirin. Nümunə: Social/Forums" }, "newLoginNudgeTitle": { "message": "Avto-doldurma ilə vaxta qənaət edin" diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 3a6054c2b52..6300ed3391f 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -3707,13 +3707,13 @@ "message": "Преместване" }, "newFolder": { - "message": "New folder" + "message": "Нова папка" }, "folderName": { - "message": "Folder Name" + "message": "Име на папката" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Можете да вложите една папка в друга като въведете името на горната папка, а след това „/“. Пример: Социални/Форуми" }, "newLoginNudgeTitle": { "message": "Спестете време с автоматично попълване" diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 5f01551a867..49cfc8823b7 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -3707,13 +3707,13 @@ "message": "Přesunout" }, "newFolder": { - "message": "New folder" + "message": "Nová složka" }, "folderName": { - "message": "Folder Name" + "message": "Název složky" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Vnořte složku přidáním názvu nadřazené složky následovaného znakem \"/\". Příklad: Sociální/Fóra" }, "newLoginNudgeTitle": { "message": "Ušetřete čas s automatickým vyplňováním" diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 5a01344185a..da1272ef300 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -3707,13 +3707,13 @@ "message": "Verschieben" }, "newFolder": { - "message": "New folder" + "message": "Neuer Ordner" }, "folderName": { - "message": "Folder Name" + "message": "Ordnername" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Verschachtel einen Ordner, indem du den Namen des übergeordneten Ordners hinzufügst, gefolgt von einem „/“. Beispiel: Sozial/Foren" }, "newLoginNudgeTitle": { "message": "Spare Zeit mit Auto-Ausfüllen" diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 8091cd72225..e56b62f9840 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -3707,13 +3707,13 @@ "message": "Áthelyezés" }, "newFolder": { - "message": "New folder" + "message": "Új mappa" }, "folderName": { - "message": "Folder Name" + "message": "Mappanév" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Mappa beágyazása a szülőmappa nevének hozzáadásával, majd egy “/” karakterrel. Példa: Közösségi/Fórumok" }, "newLoginNudgeTitle": { "message": "Idő megtakarítás automatikus kitöltéssel" diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index a1e76b5a521..437aba39714 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -3707,13 +3707,13 @@ "message": "Pārvietot" }, "newFolder": { - "message": "New folder" + "message": "Jauna mape" }, "folderName": { - "message": "Folder Name" + "message": "Mapes nosaukums" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Apakšmapes var izveidot, ja pievieno iekļaujošās mapes nosaukumu, aiz kura ir \"/\". Piemēram: Tīklošanās/Forumi" }, "newLoginNudgeTitle": { "message": "Laika ietaupīšana ar automātisko aizpildi" diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index b61c03462a3..0e58487ae88 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -3707,13 +3707,13 @@ "message": "Переместить" }, "newFolder": { - "message": "New folder" + "message": "Новая папка" }, "folderName": { - "message": "Folder Name" + "message": "Название папки" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Создайте вложенную папку, добавив название родительской папки и символ \"/\". Пример: Сообщества/Форумы" }, "newLoginNudgeTitle": { "message": "Экономьте время с помощью автозаполнения" diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 03c362d2cb3..d84b164e749 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -3707,13 +3707,13 @@ "message": "Presunúť" }, "newFolder": { - "message": "New folder" + "message": "Nový priečinok" }, "folderName": { - "message": "Folder Name" + "message": "Názov priečinka" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Vnorte priečinok pridaním názvu nadradeného priečinka a znaku \"/\". Príklad: Sociálne siete/Fóra" }, "newLoginNudgeTitle": { "message": "Ušetrite čas s automatickým vypĺňaním" diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 35fd2be4706..c6a59afda23 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -3707,13 +3707,13 @@ "message": "Taşı" }, "newFolder": { - "message": "New folder" + "message": "Yeni klasör" }, "folderName": { - "message": "Folder Name" + "message": "Klasör adı" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Üst klasörün adının sonuna “/” ekleyerek klasörleri iç içe koyabilirsiniz. Örnek: Sosyal/Forumlar" }, "newLoginNudgeTitle": { "message": "Otomatik doldurmayla zaman kazanın" diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 7146a8698c3..90cdb33fde5 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -3707,13 +3707,13 @@ "message": "移动" }, "newFolder": { - "message": "New folder" + "message": "新增文件夹" }, "folderName": { - "message": "Folder Name" + "message": "文件夹名称" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "通过在父文件夹名后面添加「/」来嵌套文件夹。示例:Social/Forums" }, "newLoginNudgeTitle": { "message": "使用自动填充节省时间" From dc776de2eb85ec5da104f87a84b85b9c3ab0ad9a Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Sat, 17 May 2025 19:04:29 +0000 Subject: [PATCH 102/245] Autosync the updated translations (#14833) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 4 + apps/browser/src/_locales/az/messages.json | 8 +- apps/browser/src/_locales/be/messages.json | 4 + apps/browser/src/_locales/bg/messages.json | 8 +- apps/browser/src/_locales/bn/messages.json | 4 + apps/browser/src/_locales/bs/messages.json | 4 + apps/browser/src/_locales/ca/messages.json | 4 + apps/browser/src/_locales/cs/messages.json | 6 +- apps/browser/src/_locales/cy/messages.json | 4 + apps/browser/src/_locales/da/messages.json | 4 + apps/browser/src/_locales/de/messages.json | 18 +- apps/browser/src/_locales/el/messages.json | 4 + apps/browser/src/_locales/en_GB/messages.json | 4 + apps/browser/src/_locales/en_IN/messages.json | 4 + apps/browser/src/_locales/es/messages.json | 4 + apps/browser/src/_locales/et/messages.json | 4 + apps/browser/src/_locales/eu/messages.json | 4 + apps/browser/src/_locales/fa/messages.json | 180 +++++++++--------- apps/browser/src/_locales/fi/messages.json | 4 + apps/browser/src/_locales/fil/messages.json | 4 + apps/browser/src/_locales/fr/messages.json | 4 + apps/browser/src/_locales/gl/messages.json | 4 + apps/browser/src/_locales/he/messages.json | 4 + apps/browser/src/_locales/hi/messages.json | 4 + apps/browser/src/_locales/hr/messages.json | 4 + apps/browser/src/_locales/hu/messages.json | 6 +- apps/browser/src/_locales/id/messages.json | 4 + apps/browser/src/_locales/it/messages.json | 4 + apps/browser/src/_locales/ja/messages.json | 4 + apps/browser/src/_locales/ka/messages.json | 4 + apps/browser/src/_locales/km/messages.json | 4 + apps/browser/src/_locales/kn/messages.json | 4 + apps/browser/src/_locales/ko/messages.json | 4 + apps/browser/src/_locales/lt/messages.json | 4 + apps/browser/src/_locales/lv/messages.json | 6 +- apps/browser/src/_locales/ml/messages.json | 4 + apps/browser/src/_locales/mr/messages.json | 4 + apps/browser/src/_locales/my/messages.json | 4 + apps/browser/src/_locales/nb/messages.json | 4 + apps/browser/src/_locales/ne/messages.json | 4 + apps/browser/src/_locales/nl/messages.json | 4 + apps/browser/src/_locales/nn/messages.json | 4 + apps/browser/src/_locales/or/messages.json | 4 + apps/browser/src/_locales/pl/messages.json | 6 +- apps/browser/src/_locales/pt_BR/messages.json | 4 + apps/browser/src/_locales/pt_PT/messages.json | 4 + apps/browser/src/_locales/ro/messages.json | 4 + apps/browser/src/_locales/ru/messages.json | 6 +- apps/browser/src/_locales/si/messages.json | 4 + apps/browser/src/_locales/sk/messages.json | 6 +- apps/browser/src/_locales/sl/messages.json | 4 + apps/browser/src/_locales/sr/messages.json | 4 + apps/browser/src/_locales/sv/messages.json | 4 + apps/browser/src/_locales/te/messages.json | 4 + apps/browser/src/_locales/th/messages.json | 4 + apps/browser/src/_locales/tr/messages.json | 6 +- apps/browser/src/_locales/uk/messages.json | 4 + apps/browser/src/_locales/vi/messages.json | 4 + apps/browser/src/_locales/zh_CN/messages.json | 6 +- apps/browser/src/_locales/zh_TW/messages.json | 4 + apps/browser/store/locales/fa/copy.resx | 60 +++--- 61 files changed, 377 insertions(+), 137 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 3502f0fff4e..c96f4a5803b 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 81cbdfd8040..51eb1345466 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "Yeni element, yeni bir pəncərədə açılır", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Saxlamazdan əvvəl düzəliş et", "description": "Tooltip and Aria label for edit button on cipher item" @@ -1097,7 +1101,7 @@ "description": "Shown to user after item is updated." }, "selectItemAriaLabel": { - "message": "Select $ITEMTYPE$, $ITEMNAME$", + "message": "$ITEMTYPE$, $ITEMNAME$ seç", "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", "placeholders": { "itemType": { @@ -1117,7 +1121,7 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "Bu girişi saxlamaq üçün kilidi açın", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 1c7fb4ff57c..a51b96547da 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index e61e8c8567e..09e5260f9b3 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "Нов елемент, отваря се в нов прозорец", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Редактиране преди запазване", "description": "Tooltip and Aria label for edit button on cipher item" @@ -1097,7 +1101,7 @@ "description": "Shown to user after item is updated." }, "selectItemAriaLabel": { - "message": "Select $ITEMTYPE$, $ITEMNAME$", + "message": "Избиране на $ITEMTYPE$, $ITEMNAME$", "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", "placeholders": { "itemType": { @@ -1117,7 +1121,7 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "Отключете, за да запазите тези данни за вписване", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index ab9b880109e..5e19936e975 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 940c886fab5..f48037814e5 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index e8d276e67e7..151412c02ed 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index aa22beab63a..effc4c950c6 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "Nová položka, otevře se v novém okně", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Upravit před uložením", "description": "Tooltip and Aria label for edit button on cipher item" @@ -1117,7 +1121,7 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "Odemknout pro uložení tohoto přihlášení", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 3e037ae58d7..047b45dd9b8 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index edba6e56f4e..e6233b1f8db 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index a739a568479..1eac22c919c 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "Neuer Eintrag, öffnet sich in neuem Fenster", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Vor dem Speichern bearbeiten", "description": "Tooltip and Aria label for edit button on cipher item" @@ -1097,7 +1101,7 @@ "description": "Shown to user after item is updated." }, "selectItemAriaLabel": { - "message": "Select $ITEMTYPE$, $ITEMNAME$", + "message": "$ITEMTYPE$, $ITEMNAME$ auswählen", "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", "placeholders": { "itemType": { @@ -1117,7 +1121,7 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "Entsperren, um diese Zugangsdaten zu speichern", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { @@ -5265,7 +5269,7 @@ "message": "Speicher eine unbegrenzte Anzahl von Passwörtern auf unbegrenzt vielen Geräten mit Bitwarden-Apps für Smartphones, Browser und Desktop." }, "nudgeBadgeAria": { - "message": "1 notification" + "message": "1 Benachrichtigung" }, "emptyVaultNudgeTitle": { "message": "Vorhandene Passwörter importieren" @@ -5280,13 +5284,13 @@ "message": "Willkommen in deinem Tresor!" }, "hasItemsVaultNudgeBodyOne": { - "message": "Autofill items for the current page" + "message": "Einträge für die aktuelle Seite automatisch ausfüllen" }, "hasItemsVaultNudgeBodyTwo": { - "message": "Favorite items for easy access" + "message": "Favoriten-Einträge für einfachen Zugriff" }, "hasItemsVaultNudgeBodyThree": { - "message": "Search your vault for something else" + "message": "Deinen Tresor nach etwas anderem durchsuchen" }, "newLoginNudgeTitle": { "message": "Spare Zeit mit Auto-Ausfüllen" @@ -5338,6 +5342,6 @@ "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "noPermissionsViewPage": { - "message": "You do not have permissions to view this page. Try logging in with a different account." + "message": "Du hast keine Berechtigung, diese Seite anzuzeigen. Versuche dich mit einem anderen Konto anzumelden." } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 723631f4d53..faad1a90a07 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index ac95545676c..d131d3ec33d 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 008c93e4397..aa1c076b2e9 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 1ab96b99154..3018cd36ed4 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 15e07b5c508..5bce2142219 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 0c9af65128b..54b7b9b234c 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 2e2ce5203aa..f132b61fc4e 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -3,7 +3,7 @@ "message": "Bitwarden" }, "appLogoLabel": { - "message": "Bitwarden logo" + "message": "لوگو Bitwarden" }, "extName": { "message": "مدیریت رمز عبور Bitwarden", @@ -17,22 +17,22 @@ "message": "وارد شوید یا یک حساب کاربری بسازید تا به گاوصندوق امن‌تان دسترسی یابید." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "دعوتنامه پذیرفته شد" }, "createAccount": { "message": "ایجاد حساب کاربری" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "در Bitwarden تازه وارد هستید؟" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "با کلید عبور وارد شوید" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "استفاده از ورود تک مرحله‌ای" }, "welcomeBack": { - "message": "Welcome back" + "message": "خوش آمدید" }, "setAStrongPassword": { "message": "تنظیم رمز عبور قوی" @@ -65,7 +65,7 @@ "message": "یادآور کلمه عبور اصلی کمک می‌کند در صورت فراموشی آن را به یاد بیارید." }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "اگر کلمه عبور خود را فراموش کنید، یادآور کلمه عبور می‌تواند به ایمیل شما ارسال شود. حداکثر $CURRENT$/$MAXIMUM$ کاراکتر.", "placeholders": { "current": { "content": "$1", @@ -84,7 +84,7 @@ "message": "یادآور کلمه عبور اصلی (اختیاری)" }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "امتیاز قدرت کلمه عبور $SCORE$", "placeholders": { "score": { "content": "$1", @@ -93,10 +93,10 @@ } }, "joinOrganization": { - "message": "Join organization" + "message": "به سازمان بپیوندید" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "به $ORGANIZATIONNAME$ بپیوندید", "placeholders": { "organizationName": { "content": "$1", @@ -105,7 +105,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "با تعیین یک کلمه عبور اصلی، عضویت خود در این سازمان را کامل کنید." }, "tab": { "message": "زبانه" @@ -132,7 +132,7 @@ "message": "کپی کلمه عبور" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "کپی عبارت عبور" }, "copyNote": { "message": "کپی یادداشت" @@ -150,31 +150,31 @@ "message": "کپی کد امنیتی" }, "copyName": { - "message": "Copy name" + "message": "کپی نام" }, "copyCompany": { - "message": "Copy company" + "message": "کپی شرکت" }, "copySSN": { - "message": "Copy Social Security number" + "message": "کپی شماره کد ملی" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "کپی شماره گذرنامه" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "کپی شماره گواهینامه" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "کپی کلید خصوصی" }, "copyPublicKey": { - "message": "Copy public key" + "message": "کپی کلید عمومی" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "کپی اثر انگشت" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "کپی $FIELD$", "placeholders": { "field": { "content": "$1", @@ -183,17 +183,17 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "کپی وب‌سایت" }, "copyNotes": { - "message": "Copy notes" + "message": "کپی یادداشت‌ها" }, "copy": { - "message": "Copy", + "message": "کپی", "description": "Copy to clipboard" }, "fill": { - "message": "Fill", + "message": "پر کردن", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { @@ -209,10 +209,10 @@ "message": "پر کردن خودکار هویت" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "پر کردن کد تأیید" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "پر کردن کد تأیید", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -255,16 +255,16 @@ "message": "افزودن مورد" }, "accountEmail": { - "message": "Account email" + "message": "حساب ایمیل" }, "requestHint": { - "message": "Request hint" + "message": "درخواست راهنمایی" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "درخواست یادآور کلمه عبور" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" + "message": "نشانی ایمیل حساب کاربری خود را وارد کنید تا راهنمای کلمه عبور برای شما ارسال شود" }, "getMasterPasswordHint": { "message": "دریافت یادآور کلمه عبور اصلی" @@ -291,13 +291,13 @@ "message": "تغییر کلمه عبور اصلی" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "با برنامه وب ادامه می‌دهید؟" }, "continueToWebAppDesc": { "message": "ویژگی‌های بیشتر حساب Bitwarden خود را در برنامه وب کاوش کنید." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "به مرکز راهنمایی ادامه می‌دهید؟" }, "continueToHelpCenterDesc": { "message": "درباره استفاده از Bitwarden در مرکز راهنما بیشتر بیاموزید." @@ -309,7 +309,7 @@ "message": "به دیگران کمک کنید تا بفهمند آیا Bitwarden برایشان مناسب است یا نه. به فروشگاه افزونه مرورگر خود بروید و نظر خود را به اشتراک بگذارید." }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "می‌توانید کلمه عبور اصلی خود را در برنامه وب Bitwarden تغییر دهید." }, "fingerprintPhrase": { "message": "عبارت اثر انگشت", @@ -332,10 +332,10 @@ "message": "درباره" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "موارد بیشتر از Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "به bitwarden.com ادامه می‌دهید؟" }, "bitwardenForBusiness": { "message": "Bitwarden برای کسب و کارها" @@ -344,25 +344,25 @@ "message": "تاییدکننده هویت Bitwarden" }, "continueToAuthenticatorPageDesc": { - "message": "Bitwarden Authenticator allows you to store authenticator keys and generate TOTP codes for 2-step verification flows. Learn more on the bitwarden.com website" + "message": "احراز هویت کننده Bitwarden به شما امکان می‌دهد کلیدهای احراز هویت را ذخیره کرده و کدهای TOTP را برای فرآیندهای تأیید دومرحله‌ای تولید کنید. برای اطلاعات بیشتر به وب‌سایت bitwarden.com مراجعه کنید" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "مدیر اسرار Bitwarden" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "اسرار توسعه دهنده را با مدیر اسرا Bitwarden به‌صورت ایمن ذخیره، مدیریت و به اشتراک بگذارید. برای اطلاعات بیشتر به وب‌سایت bitwarden.com مراجعه کنید." }, "passwordlessDotDev": { "message": "Passwordless.dev" }, "continueToPasswordlessDotDevPageDesc": { - "message": "Create smooth and secure login experiences free from traditional passwords with Passwordless.dev. Learn more on the bitwarden.com website." + "message": "با استفاده از Passwordless.dev تجربه‌های ورود روان و ایمنی را بدون نیاز به کلمات عبور سنتی ایجاد کنید. برای اطلاعات بیشتر به وب‌سایت bitwarden.com مراجعه کنید." }, "freeBitwardenFamilies": { "message": "خانواده‌های رایگان Bitwarden" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "شما واجد شرایط استفاده رایگان از Bitwarden Families هستید. این پیشنهاد را امروز در نسخه وب دریافت کنید." }, "version": { "message": "نسخه" @@ -383,7 +383,7 @@ "message": "ويرايش پوشه" }, "editFolderWithName": { - "message": "Edit folder: $FOLDERNAME$", + "message": "ویرایش پوشه: $FOLDERNAME$", "placeholders": { "foldername": { "content": "$1", @@ -392,22 +392,22 @@ } }, "newFolder": { - "message": "New folder" + "message": "پوشه جدید" }, "folderName": { - "message": "Folder name" + "message": "نام پوشه" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "برای تو در تو کردن یک پوشه، نام پوشه والد را وارد کرده و سپس یک “/” اضافه کنید. مثال: Social/Forums" }, "noFoldersAdded": { - "message": "No folders added" + "message": "هیچ پوشه‌ای اضافه نشد" }, "createFoldersToOrganize": { - "message": "Create folders to organize your vault items" + "message": "برای سامان‌دهی موردهای گاوصندوق خود پوشه ایجاد کنید" }, "deleteFolderPermanently": { - "message": "Are you sure you want to permanently delete this folder?" + "message": "مطمئنید می‌خواهید این پوشه را برای همیشه پاک کنید؟" }, "deleteFolder": { "message": "حذف پوشه" @@ -450,7 +450,7 @@ "message": "به طور خودکار کلمه‌های عبور قوی و منحصر به فرد برای ورود به سیستم خود ایجاد کنید." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "برنامه وب Bitwarden" }, "importItems": { "message": "درون ریزی موارد" @@ -462,19 +462,19 @@ "message": "تولید کلمه عبور" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "تولید عبارت عبور" }, "passwordGenerated": { - "message": "Password generated" + "message": "کلمه عبور تولید شد" }, "passphraseGenerated": { - "message": "Passphrase generated" + "message": "عبارت عبور تولید شد" }, "usernameGenerated": { - "message": "Username generated" + "message": "نام کاربری تولید شد" }, "emailGenerated": { - "message": "Email generated" + "message": "ایمیل تولید شد" }, "regeneratePassword": { "message": "تولید مجدد کلمه عبور" @@ -486,11 +486,11 @@ "message": "طول" }, "include": { - "message": "Include", + "message": "شامل", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "شامل حروف بزرگ باشد", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -498,7 +498,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "شامل حروف کوچک باشد", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -506,7 +506,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "شامل اعداد", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -514,7 +514,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "افزودن کاراکترهای خاص", "description": "Full description for the password generator special characters checkbox" }, "numWords": { @@ -537,11 +537,11 @@ "message": "حداقل حرف خاص" }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "از کاراکترهای مبهم خودداری کن", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "نیازمندی‌های سیاست سازمانی بر گزینه‌های تولید کننده شما اعمال شده‌اند.", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { @@ -566,7 +566,7 @@ "message": "کلمه عبور" }, "totp": { - "message": "Authenticator secret" + "message": "کلید مخفی احراز کننده هویت‌" }, "passphrase": { "message": "عبارت عبور" @@ -587,7 +587,7 @@ "message": "یادداشت‌ها" }, "privateNote": { - "message": "Private note" + "message": "یادداشت خصوصی" }, "note": { "message": "یادداشت" @@ -608,10 +608,10 @@ "message": "راه اندازی" }, "launchWebsite": { - "message": "Launch website" + "message": "باز کردن وب‌سایت" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "باز کردن وب‌سایت $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -632,19 +632,19 @@ "message": "ساير" }, "unlockMethods": { - "message": "Unlock options" + "message": "باز کردن امکانات" }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "یک روش بازگشایی برای پایان زمان مجاز تنظیم کنید." }, "unlockMethodNeeded": { - "message": "Set up an unlock method in Settings" + "message": "یک روش باز کردن قفل را در تنظیمات راه‌اندازی کنید" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "پایان زمان نشست" }, "vaultTimeoutHeader": { - "message": "Vault timeout" + "message": "متوقف شدن گاو‌صندوق" }, "otherOptions": { "message": "سایر گزینه‌ها" @@ -656,25 +656,25 @@ "message": "مرورگر شما از کپی کلیپ بورد آسان پشتیبانی نمی‌کند. به جای آن به صورت دستی کپی کنید." }, "verifyYourIdentity": { - "message": "Verify your identity" + "message": "هویت خود را تأیید کنید" }, "weDontRecognizeThisDevice": { - "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + "message": "ما این دستگاه را نمی‌شناسیم. برای تأیید هویت خود، کدی را که به ایمیلتان ارسال شده وارد کنید." }, "continueLoggingIn": { - "message": "Continue logging in" + "message": "ادامه ورود" }, "yourVaultIsLocked": { "message": "گاوصندوق شما قفل شده است. برای ادامه هویت خود را تأیید کنید." }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "گاوصندوق‌تان قفل شد" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "حساب شما قفل شده است" }, "or": { - "message": "or" + "message": "یا" }, "unlock": { "message": "باز کردن قفل" @@ -699,13 +699,13 @@ "message": "متوقف شدن گاو‌صندوق" }, "vaultTimeout1": { - "message": "Timeout" + "message": "پایان زمان" }, "lockNow": { "message": "الان قفل شود" }, "lockAll": { - "message": "Lock all" + "message": "قفل کردن همه" }, "immediately": { "message": "بلافاصله" @@ -753,16 +753,16 @@ "message": "امنیت" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "تأیید کلمه عبور اصلی" }, "masterPassword": { - "message": "Master password" + "message": "کلمه عبور اصلی" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "کلمه‌های عبور اصلی در صورت فراموشی قابل بازیابی نیستند!" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "یادآور کلمه عبور اصلی" }, "errorOccurred": { "message": "خطایی رخ داده است" @@ -796,10 +796,10 @@ "message": "حساب کاربری جدید شما ساخته شد! حالا می‌توانید وارد شوید." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "حساب جدید شما ایجاد شده است!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "شما با موفقیت وارد شدید!" }, "youSuccessfullyLoggedIn": { "message": "شما با موفقیت وارد شدید" @@ -814,7 +814,7 @@ "message": "کد تأیید مورد نیاز است." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "احراز هویت لغو شد یا بیش از حد طول کشید. لطفاً دوباره تلاش کنید." }, "invalidVerificationCode": { "message": "کد تأیید نامعتبر است" @@ -833,7 +833,7 @@ "message": "ناتوان در پر کردن خودکار مورد انتخاب شده در این صفحه. اطلاعات را کپی و جای‌گذاری کنید." }, "totpCaptureError": { - "message": "Unable to scan QR code from the current webpage" + "message": "امکان اسکن کد QR از صفحه وب فعلی وجود ندارد" }, "totpCaptureSuccess": { "message": "کلید احراز هویت اضافه شد" @@ -890,10 +890,10 @@ "message": "Follow the steps below to finish logging in with your security key." }, "restartRegistration": { - "message": "Restart registration" + "message": "ثبت‌نام را دوباره آغاز کنید" }, "expiredLink": { - "message": "Expired link" + "message": "پیوند منقضی شد" }, "pleaseRestartRegistrationOrTryLoggingIn": { "message": "Please restart registration or try logging in." @@ -911,7 +911,7 @@ "message": "خیر" }, "location": { - "message": "Location" + "message": "موقعیت" }, "unexpectedError": { "message": "یک خطای غیر منتظره رخ داده است." @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index f28d82c201e..ead59384ff8 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Muokkaa ennen tallentamista", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index b15713a07a4..a7734899843 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 125cb19ea25..8a825082b5f 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index f65a26d2d1d..91a5121db16 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index d25d7c9f2dd..2e42a819b65 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 77a0b90c6be..8af1aa70cc3 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 8bf191cf6ec..557e465ea19 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index eabc67cde49..b47093bc2ee 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "Új elem, megnyitás új ablakban", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Szerkesztés mentés előtt", "description": "Tooltip and Aria label for edit button on cipher item" @@ -1117,7 +1121,7 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "Feloldás a bejelentkezés mentéséhez", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 8d71d7a6ba2..03e47fca575 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Sunting sebelum menyimpan", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 16b77251cf4..d3d0dc9ec43 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 29455d9515b..e9c22c1286f 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index a888e84a154..d8397db5da8 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 61a6d73e52c..f436c45ab75 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 737d91430b5..0459e007126 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index fd957ef5061..8add57d61ec 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 46b8eaddee1..06ff7cda8fa 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 24887061e71..e9044f4c041 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "Jauns vienums, atvērsies jaunā logā", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Labot pirms saglabāšanas", "description": "Tooltip and Aria label for edit button on cipher item" @@ -1117,7 +1121,7 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "Jāatslēdz, lai saglabātu šo pieteikšanās vienumu", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 02c6e5ee14c..e6de809dfbd 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 4b418eff676..dd347d822c7 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 61a6d73e52c..f436c45ab75 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index c4319d140da..62b55eb6501 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Rediger før du lagrer", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 61a6d73e52c..f436c45ab75 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index d16080d962a..af637b2f4cd 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "Nieuw Item, opent in nieuw venster", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Bewerken voor opslaan", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 61a6d73e52c..f436c45ab75 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 61a6d73e52c..f436c45ab75 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index bf6b4d8b70c..16696e5f828 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "Nowy element, otwiera się w nowym oknie", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edytuj przed zapisaniem", "description": "Tooltip and Aria label for edit button on cipher item" @@ -1117,7 +1121,7 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "Odblokuj, aby zapisać ten login", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 293e72b6dc0..e5ca45500a5 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index d29f5848153..a4ed095c4db 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "Novo item, abre numa nova janela", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Editar antes de guardar", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 08b0c93b2db..71990435ad2 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index f4e53e8c0d6..2d23c5352e5 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "Новый элемент, откроется в новом окне", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Изменить перед сохранением", "description": "Tooltip and Aria label for edit button on cipher item" @@ -1117,7 +1121,7 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "Разблокировать, чтобы сохранить этот логин", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index da35b2cc13f..308404946b3 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index a71aed7a8a2..e8d2993a8df 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "Nová položka, otvorí sa v novom okne", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Upraviť pred uložením", "description": "Tooltip and Aria label for edit button on cipher item" @@ -1117,7 +1121,7 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "Odomknúť na uloženie prihlasovacích údajov", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 8ef5f1aef09..65ee31c888c 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index a60766ca944..255b8dfb924 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Уреди пре сачувавања", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index c6280ce5a44..3a063f8f066 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 61a6d73e52c..f436c45ab75 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index e528897676d..19430463090 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 0899f4bb196..700e9b73881 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "Yeni kayıt (yeni pencerede açılır)", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Kaydetmeden önce düzenle", "description": "Tooltip and Aria label for edit button on cipher item" @@ -1117,7 +1121,7 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "Bu hesabı kaydetmek için kilidi açın", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 6b7e6a18aaf..b0c78a17106 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Редагувати перед збереженням", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index afd801839a8..14649828aaf 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 73c7b9a4b0d..e0bc34bd47d 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "新增项目,在新窗口中打开", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "保存前编辑", "description": "Tooltip and Aria label for edit button on cipher item" @@ -1117,7 +1121,7 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "解锁以保存此登录", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 4d58c479838..e9eab0a726d 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -1071,6 +1071,10 @@ }, "description": "Aria label for the view button in notification bar confirmation message" }, + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, "notificationEditTooltip": { "message": "Edit before saving", "description": "Tooltip and Aria label for edit button on cipher item" diff --git a/apps/browser/store/locales/fa/copy.resx b/apps/browser/store/locales/fa/copy.resx index 69816ce3590..2d45e114719 100644 --- a/apps/browser/store/locales/fa/copy.resx +++ b/apps/browser/store/locales/fa/copy.resx @@ -118,58 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - مدیریت رمز عبور Bitwarden + مدیر کلمه عبور Bitwarden - در خانه، محل کار، یا در حال حرکت، Bitwarden به سادگی تمامی رمزهای عبور، passkeyها، و اطلاعات حساس شما را امن نگاه می‌دارد. + در خانه، محل کار، یا در حال حرکت، Bitwarden به سادگی تمامی کلمات عبور، کلیدها، و اطلاعات حساس شما را امن نگاه می‌دارد. - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + شناخته‌شده به‌عنوان بهترین مدیر کلمه عبور توسط PCMag، WIRED، The Verge، CNET، G2 و دیگر منابع! -SECURE YOUR DIGITAL LIFE -Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. +امنیت زندگی دیجیتال شما +زندگی دیجیتال خود را ایمن کنید و با تولید و ذخیره کلمات عبور قوی و منحصر به فرد برای هر حساب کاربری، از نشت اطلاعات جلوگیری نمایید. همه چیز را در یک گاوصندوق کلمه عبور با رمزگذاری سرتاسری ذخیره کنید که فقط شما به آن دسترسی دارید. -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +دسترسی به داده‌های شما، در هر زمان، در هر مکان، روی هر دستگاه +کلمات عبور را به‌صورت نامحدود روی دستگاه‌های مختلف مدیریت، ذخیره، ایمن‌سازی و به اشتراک بگذارید بدون هیچ محدودیتی. -EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE -Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. +همه باید ابزارهای ایمنی آنلاین را در اختیار داشته باشند +از Bitwarden به‌صورت رایگان استفاده کنید، بدون تبلیغات یا فروش داده‌ها. Bitwarden باور دارد که همه باید بتوانند در فضای آنلاین ایمن بمانند. طرح‌های پریمیوم، ویژگی‌های پیشرفته‌تری ارائه می‌دهند. -EMPOWER YOUR TEAMS WITH BITWARDEN -Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. +توانمندسازی تیم‌ها با Bitwarden +طرح‌های ویژه تیم‌ها و سازمان‌ها شامل ویژگی‌های حرفه‌ای برای کسب‌وکار هستند، مانند: ادغام SSO، میزبانی شخصی، ادغام با دایرکتوری و فراهم‌سازی SCIM، سیاست‌های جهانی، دسترسی API، گزارش رویدادها و موارد دیگر. -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +از Bitwarden برای ایمن‌سازی محیط کاری و اشتراک‌گذاری اطلاعات حساس با همکاران استفاده کنید. +دلایل بیشتر برای انتخاب Bitwarden: -More reasons to choose Bitwarden: +رمزگذاری سطح جهانی + کلمات عبور با رمزگذاری پیشرفته سرتاسری (AES-256 بیت، هش نمکی، و PBKDF2 SHA-256) محافظت می‌شوند تا داده‌های شما امن و خصوصی باقی بمانند. -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. +ممیزی‌های امنیتی توسط اشخاص ثالث + Bitwarden به‌طور منظم توسط شرکت‌های معتبر امنیتی مورد ارزیابی و تست نفوذ قرار می‌گیرد. این بررسی‌ها شامل بررسی کد منبع و تست‌های امنیتی روی IPها، سرورها و اپلیکیشن‌های وب Bitwarden است. -3rd-party Audits -Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. - -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +احراز هویت دومرحله‌ای پیشرفته (2FA) + ورود امن با استفاده از احراز هویت‌کننده‌های شخص ثالث، کدهای ایمیلی، یا گواهی‌نامه‌های FIDO2 WebAuthn مانند کلید امنیتی سخت‌افزاری یا کلید عبور. Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + ارسال مستقیم داده‌ها با رمزگذاری سرتاسری، با امکان محدودسازی دسترسی و مدت زمان مشاهده. -Built-in Generator -Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. +مولد داخلی + تولید کلمات عبور بلند، پیچیده و منحصربه‌فرد و همچنین نام‌های کاربری برای هر وب‌سایتی که بازدید می‌کنید. امکان ادغام با ارائه‌دهندگان ایمیل مستعار برای حفظ بیشتر حریم خصوصی. -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +ترجمه‌های جهانی + Bitwarden به بیش از ۶۰ زبان در دسترس است که توسط جامعه جهانی از طریق Crowdin ترجمه شده‌اند. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +اپلیکیشن‌های چند سکویی + ایمن‌سازی و اشتراک‌گذاری داده‌ها از طریق هر مرورگر، دستگاه موبایل یا سیستم‌عامل دسکتاپ و موارد دیگر. -Bitwarden secures more than just passwords -End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! +Bitwarden فقط کلمات عبور را ایمن نمی‌کند +راهکارهای مدیریت اعتبارات با رمزگذاری سرتاسری از Bitwarden به سازمان‌ها کمک می‌کند تا همه چیز را ایمن کنند، از جمله اسرار توسعه‌دهنده و تجربه‌های بدون کلمات عبور. +برای اطلاعات بیشتر درباره Bitwarden Secrets Manager و Bitwarden Passwordless.dev به وب‌سایت Bitwarden.com مراجعه کنید! - در خانه، محل کار، یا در حال حرکت، Bitwarden به سادگی تمامی رمزهای عبور، passkeyها، و اطلاعات حساس شما را امن نگاه می‌دارد. + در خانه، محل کار، یا در حال حرکت، Bitwarden به سادگی تمامی کلمات عبور، کلیدها، و اطلاعات حساس شما را امن نگاه می‌دارد. همگام‌سازی و دسترسی به گاوصندوق خود از دستگاه های مختلف From 4079607a3e07cadd5ce5289225c4b5faa30bac47 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Sat, 17 May 2025 21:38:05 +0200 Subject: [PATCH 103/245] Autosync the updated translations (#14835) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 7 +- apps/web/src/locales/ar/messages.json | 7 +- apps/web/src/locales/az/messages.json | 7 +- apps/web/src/locales/be/messages.json | 7 +- apps/web/src/locales/bg/messages.json | 7 +- apps/web/src/locales/bn/messages.json | 7 +- apps/web/src/locales/bs/messages.json | 7 +- apps/web/src/locales/ca/messages.json | 7 +- apps/web/src/locales/cs/messages.json | 7 +- apps/web/src/locales/cy/messages.json | 7 +- apps/web/src/locales/da/messages.json | 7 +- apps/web/src/locales/de/messages.json | 15 +- apps/web/src/locales/el/messages.json | 7 +- apps/web/src/locales/en_GB/messages.json | 7 +- apps/web/src/locales/en_IN/messages.json | 7 +- apps/web/src/locales/eo/messages.json | 59 +++---- apps/web/src/locales/es/messages.json | 7 +- apps/web/src/locales/et/messages.json | 7 +- apps/web/src/locales/eu/messages.json | 7 +- apps/web/src/locales/fa/messages.json | 7 +- apps/web/src/locales/fi/messages.json | 7 +- apps/web/src/locales/fil/messages.json | 7 +- apps/web/src/locales/fr/messages.json | 7 +- apps/web/src/locales/gl/messages.json | 7 +- apps/web/src/locales/he/messages.json | 7 +- apps/web/src/locales/hi/messages.json | 7 +- apps/web/src/locales/hr/messages.json | 7 +- apps/web/src/locales/hu/messages.json | 7 +- apps/web/src/locales/id/messages.json | 7 +- apps/web/src/locales/it/messages.json | 7 +- apps/web/src/locales/ja/messages.json | 7 +- apps/web/src/locales/ka/messages.json | 7 +- apps/web/src/locales/km/messages.json | 7 +- apps/web/src/locales/kn/messages.json | 7 +- apps/web/src/locales/ko/messages.json | 7 +- apps/web/src/locales/lv/messages.json | 7 +- apps/web/src/locales/ml/messages.json | 7 +- apps/web/src/locales/mr/messages.json | 7 +- apps/web/src/locales/my/messages.json | 7 +- apps/web/src/locales/nb/messages.json | 7 +- apps/web/src/locales/ne/messages.json | 7 +- apps/web/src/locales/nl/messages.json | 7 +- apps/web/src/locales/nn/messages.json | 7 +- apps/web/src/locales/or/messages.json | 7 +- apps/web/src/locales/pl/messages.json | 7 +- apps/web/src/locales/pt_BR/messages.json | 7 +- apps/web/src/locales/pt_PT/messages.json | 7 +- apps/web/src/locales/ro/messages.json | 189 ++++++++++++----------- apps/web/src/locales/ru/messages.json | 7 +- apps/web/src/locales/si/messages.json | 7 +- apps/web/src/locales/sk/messages.json | 9 +- apps/web/src/locales/sl/messages.json | 7 +- apps/web/src/locales/sr/messages.json | 7 +- apps/web/src/locales/sr_CS/messages.json | 7 +- apps/web/src/locales/sv/messages.json | 7 +- apps/web/src/locales/te/messages.json | 7 +- apps/web/src/locales/th/messages.json | 7 +- apps/web/src/locales/tr/messages.json | 7 +- apps/web/src/locales/uk/messages.json | 7 +- apps/web/src/locales/vi/messages.json | 7 +- apps/web/src/locales/zh_CN/messages.json | 9 +- apps/web/src/locales/zh_TW/messages.json | 7 +- 62 files changed, 433 insertions(+), 247 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index fe109d6c2f0..152eb51d7d1 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Enige geënkripteerde uitsture wat u bewaar het word ook ongeldig." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Intekening" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index b12ff40f7a6..c84858fce08 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index fa35fc3e6ed..3518898335e 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Şifrələmə açarını güncəllədikdən sonra, hazırda istifadə etdiyiniz (mobil tətbiq və ya brauzer uzantıları kimi) bütün Bitwarden tətbiqlərində çıxış edib yenidən giriş etməlisiniz. Çıxış edib təkrar giriş etməmək (yeni şifrələmə açarının endirilməsi prosesi) dataların zədələnməsi ilə nəticələnə bilər. Avtomatik olaraq çıxış etməyə çalışacağıq, bu gecikə bilər." }, - "updateEncryptionKeyExportWarning": { - "message": "Saxladığınız bütün şifrələmə ixracları da yararsız olacaq." + "updateEncryptionKeyAccountExportWarning": { + "message": "Məhdudiyyətli hesablara aid saxladığınız xaricə köçürmələr yararsız olacaq." }, "subscription": { "message": "Abunəlik" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Bank hesabı ilə ödəniş, yalnız Amerika Birləşmiş Ştatlarındakı müştərilər üçün əlçatandır. Bank hesabınızı doğrulamağınız tələb olunacaq. Növbəti 1-2 iş günü ərzində mikro depozit qoyacağıq. Bank hesabını doğrulamaq üçün bu depozitdəki çıxarış deskriptor kodunu təşkilatın abunəlik səhifəsində daxil edin. Bank hesabı doğrulanmadıqda ödəniş buraxılacaq və abunəliyiniz dayandırılacaq." + }, + "clickPayWithPayPal": { + "message": "Ödəniş üsulunuzu əlavə etmək üçün lütfən Paypal ilə ödəniş et düyməsinə klikləyin." } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 3e78155f7ba..789f292aeaf 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Пасля абнаўлення вашага ключа шыфравання вам неабходна выйсці з сістэмы, а потым выканаць паўторны ўваход ва ўсе праграмы Bitwarden, якія вы зараз выкарыстоўваеце (напрыклад, мабільныя праграмы або пашырэнні для браўзераў). Збой пры выхадзе і паўторным уваходзе (пры гэтым спампоўваецца ваш новы ключ шыфравання) можа стаць прычынай пашкоджання даных. Мы паспрабуем аўтаматычна ажыццявіць завяршэнне ўсіх вашых сеансаў, але гэта можа адбывацца з затрымкай." }, - "updateEncryptionKeyExportWarning": { - "message": "Любыя зашыфраваныя экспартаванні, якія вы захавалі, таксама стануць памылковымі." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Падпіска" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index ee858d0a23f..b26d6a21578 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "След смяната на ключа за шифриране ще трябва да се отпишете и след това да се впишете в регистрацията си във всички приложения на Битуорден, които ползвате (като мобилното приложение и разширенията за браузъри). Ако не се отпишете и впишете повторно (за да получите достъп до новия ключ), рискувате да повредите записите си. Сега ще се пробва да бъдете отписани автоматично, това обаче може да се забави." }, - "updateEncryptionKeyExportWarning": { - "message": "Всички шифрирани вече изнесени данни ще станат безполезни." + "updateEncryptionKeyAccountExportWarning": { + "message": "Всички изнасяния ограничени от акаунта, които сте запазили, ще станат невалидни." }, "subscription": { "message": "Абонамент" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Плащането чрез банкова сметка е налично само за клиенти от САЩ. Ще трябва да потвърдите банковата си сметка. В следващите 1-2 работни дни ще направим малък депозит. Въведете кода от описанието на трансакцията в страницата за абонаменти на доставчика, за да потвърдите банковата сметка. Ако не потвърдите банковата сметка, може да пропуснете плащането и абонаментът Ви да бъде спрян." + }, + "clickPayWithPayPal": { + "message": "Моля, натиснете бутона за плащане с PayPal, за да добавите платежния си метод." } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index d09919e0f3c..bb71e44f09d 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 35d8401a8d2..a05e84245e8 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index d74d707735b..f24353d69b3 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Després d'actualitzar la vostra clau de xifratge, heu de tancar la sessió i tornar a entrar a totes les aplicacions de Bitwarden que esteu utilitzant actualment (com ara l'aplicació mòbil o les extensions del navegador). Si no es tanca i torna a iniciar la sessió (la qual descarrega la vostra nova clau de xifratge) pot provocar corrupció en les dades. Intentarem registrar-vos automàticament, però, es pot retardar." }, - "updateEncryptionKeyExportWarning": { - "message": "Totes les exportacions xifrades que hàgeu desat també seran no vàlides." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscripció" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 9f10b3c6404..4ba5a9c3fea 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Po aktualizace šifrovacího klíče dojde k odhlášení a budete se muset opětovně přihlásit do všech aplikací Bitwardenu, které aktuálně používáte (např. mobilní aplikace či rozšíření pro prohlížeč). Nezdaří-li se odhlášení a opětovné přihlášení (během něhož bude stažen nový šifrovací klíč), může dojít k poškození dat. Pokusíme se Vás automaticky odhlásit, nicméně, může to chvíli trvat." }, - "updateEncryptionKeyExportWarning": { - "message": "Jakékoli šifrované exporty, které jste uložili, budou také neplatné." + "updateEncryptionKeyAccountExportWarning": { + "message": "Všechny uložené exporty s omezením účtu se stanou neplatnými." }, "subscription": { "message": "Předplatné" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Platba bankovním účtem je dostupná jen pro zákazníky ve Spojených státech. Budete požádáni o ověření svého bankovního účtu. Mikrovklad provedeme během následujících 1-2 pracovních dnů. Pro ověření bankovního účtu zadejte kód popisu výpisu z této zálohy na stránce předplatného poskytovatele. Pokud bankovní účet neověříte, bude to mít za následek zmeškání platby a pozastavení předplatného." + }, + "clickPayWithPayPal": { + "message": "Pro přidání způsobu platby klepněte na tlačítko \"Pay with PayPal\"." } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 39dba259cc2..f91dc727a84 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 40c1283cb24..f40daec488f 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Efter opdatering af din krypteringsnøgle skal du logge ud og ind igen i alle Bitwarden-programmer, du bruger i øjeblikket (f.eks. mobilapp eller browserudvidelser). Hvis du ikke logger ud og ind (som downloader din nye krypteringsnøgle), kan det resultere i data korruption. Vi vil forsøge at logge dig ud automatisk, men det kan blive forsinket." }, - "updateEncryptionKeyExportWarning": { - "message": "Enhver krypteret eksport du har gemt, vil også blive utilgængelig." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Abonnement" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 7cdc0adc7d8..f39bb85a082 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -99,7 +99,7 @@ "message": "Anwendung als kritisch markieren" }, "applicationsMarkedAsCriticalSuccess": { - "message": "Applications marked as critical" + "message": "Als kritisch markierte Anwendungen" }, "application": { "message": "Anwendung" @@ -141,13 +141,13 @@ "message": "Diese Mitglieder melden sich bei Anwendungen mit schwachen, kompromittierten oder wiederverwendeten Passwörtern an." }, "atRiskMembersDescriptionNone": { - "message": "These are no members logging into applications with weak, exposed, or reused passwords." + "message": "Dies sind keine Mitglieder, die sich in Anwendungen mit schwachen, kompromittierten oder wiederverwendeten Passwörtern anmelden." }, "atRiskApplicationsDescription": { "message": "Diese Anwendungen haben schwache, kompromittierte oder wiederverwendete Passwörter." }, "atRiskApplicationsDescriptionNone": { - "message": "These are no applications with weak, exposed, or reused passwords." + "message": "Dies sind keine Anwendungen mit schwachen, kompromittierten oder wiederverwendeten Passwörtern." }, "atRiskMembersDescriptionWithApp": { "message": "Diese Mitglieder melden sich bei $APPNAME$ mit schwachen, kompromittierten oder wiederverwendeten Passwörtern an.", @@ -159,7 +159,7 @@ } }, "atRiskMembersDescriptionWithAppNone": { - "message": "There are no at risk members for $APPNAME$.", + "message": "Es gibt keine gefährdeten Mitglieder für $APPNAME$.", "placeholders": { "appname": { "content": "$1", @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Nach der Aktualisierung Ihres Verschlüsselungsschlüssels musst du dich bei allen Bitwarden-Anwendungen, die du momentan benutzt, erneut anmelden (wie z.B. die mobile App oder die Browser-Erweiterungen). Fehler bei Ab- und Anmeldung (die deinen neuen Verschlüsselungsschlüssel herunterlädt) könnte zu einer Beschädigung der Daten führen. Wir werden versuchen dich automatisch abzumelden, was jedoch verzögert geschehen kann." }, - "updateEncryptionKeyExportWarning": { - "message": "Alle verschlüsselten Exporte, die du gespeichert hast, werden ebenfalls ungültig." + "updateEncryptionKeyAccountExportWarning": { + "message": "Alle von dir gespeicherten Exporte mit Konto-Beschränkungen werden ungültig." }, "subscription": { "message": "Abo" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Die Zahlung mit einem Bankkonto ist nur für Kunden in den Vereinigten Staaten möglich. Du musst dein Bankkonto verifizieren. Wir werden innerhalb der nächsten 1-2 Werktage eine Mikro-Einzahlung vornehmen. Gib den Code aus der Beschreibung dieser Einzahlung auf der Abonnementseite des Anbieters ein, um das Bankkonto zu verifizieren. Schlägt die Verifizierung des Bankkontos fehl, wird dies als versäumte Zahlung gewertet und dein Abonnement gesperrt." + }, + "clickPayWithPayPal": { + "message": "Bitte klicke auf den Mit PayPal bezahlen Button, um deine Zahlungsmethode hinzuzufügen." } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index a7c423c91e0..24de28d39be 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Μετά την ενημέρωση του κλειδιού κρυπτογράφησης, πρέπει να αποσυνδεθείτε και να επιστρέψετε σε όλες τις εφαρμογές Bitwarden που χρησιμοποιείτε αυτήν τη στιγμή (όπως η εφαρμογή για κινητά ή οι επεκτάσεις του προγράμματος περιήγησης). Η αποτυχία αποσύνδεσης και επαναφοράς (στην οποία γίνεται λήψη του νέου κλειδιού κρυπτογράφησης) ενδέχεται να προκαλέσει καταστροφή δεδομένων. Θα προσπαθήσουμε να αποσυνδεθείτε αυτόματα, ωστόσο αυτό μπορεί να καθυστερήσει." }, - "updateEncryptionKeyExportWarning": { - "message": "Οποιεσδήποτε κρυπτογραφημένες εξαγωγές που έχετε αποθηκεύσει θα είναι επίσης άκυρες." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Συνδρομή" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 460501615e9..d6a1b01f1d5 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, although this may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 3a3b4391a56..a0e9f4de149 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, although this may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 0e3985b4d40..5b5e1f21582 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -925,10 +925,10 @@ "message": "Maksimuma dosiergrandeco estas 500 MB." }, "addedItem": { - "message": "La ero aldoniĝis" + "message": "Ero aldoniĝis" }, "editedItem": { - "message": "La ero redaktiĝis" + "message": "Ero redaktiĝis" }, "movedItemToOrg": { "message": "$ITEMNAME$ moviĝis al $ORGNAME$", @@ -944,7 +944,7 @@ } }, "itemsMovedToOrg": { - "message": "La eroj moviĝis al $ORGNAME$", + "message": "Eroj moviĝis al $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -953,7 +953,7 @@ } }, "itemMovedToOrg": { - "message": "La ero moviĝis al $ORGNAME$", + "message": "Ero moviĝis al $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -986,7 +986,7 @@ "message": "Ĉu vi certe volas anstataŭigi la nunan pasvorton?" }, "editedFolder": { - "message": "La dosierujo konserviĝis" + "message": "`Dosierujo konserviĝis" }, "addedFolder": { "message": "Dosierujo aldoniĝis" @@ -2866,7 +2866,7 @@ "message": "Ĉu vi certas, ke vi volas nuligi? Vi perdos aliron al ĉiuj funkcioj de ĉi tiu abono fine de ĉi tiu faktura ciklo." }, "canceledSubscription": { - "message": "La abono estis nuligita." + "message": "Abono nuliĝis" }, "neverExpires": { "message": "Neniam eksvalidiĝis" @@ -4248,7 +4248,7 @@ "message": "La organizo kaj ĉiuj rilataj datumoj estis forigitaj." }, "organizationUpdated": { - "message": "La organizo ĝisdatiĝis" + "message": "Organizo konserviĝis" }, "taxInformation": { "message": "Impostaj Informoj" @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Post ĝisdatigi vian ĉifradan ŝlosilon, vi devas elsaluti kaj reeniri al ĉiuj Bitwarden-aplikaĵoj, kiujn vi nun uzas (kiel la poŝtelefona programo aŭ retumila etendaĵoj). Malsukceso elsaluti kaj reeniri (kiu elŝutas via nova ĉifra ŝlosilo) povas rezultigi korupton de datumoj. Ni provos elsaluti vin aŭtomate, tamen ĝi eble prokrastos. " }, - "updateEncryptionKeyExportWarning": { - "message": "Ĉiuj ĉifritaj eksportaĵoj, kiujn vi konservis, ankaŭ malvalidiĝos." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Abono" @@ -4878,7 +4878,7 @@ "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { - "message": "La prefero de uzanto" + "message": "Prefero de la uzanto" }, "vaultTimeoutAction": { "message": "Volta Tempolima Ago" @@ -4949,7 +4949,7 @@ "message": "Eroj rehaviĝis" }, "restoredItemId": { - "message": "La ero $ID$ rehaviĝis", + "message": "Restariĝis ero $ID$.", "placeholders": { "id": { "content": "$1", @@ -8694,7 +8694,7 @@ } }, "collectionManagement": { - "message": "Collection management" + "message": "Administrado de kolekto" }, "collectionManagementDesc": { "message": "Manage the collection behavior for the organization" @@ -8770,13 +8770,13 @@ "message": "At least one member or group must have can manage permission." }, "typePasskey": { - "message": "Passkey" + "message": "Pasŝlosilo" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "Pasŝlosilo ne estos kopiita" }, "passkeyNotCopiedAlert": { - "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" + "message": "La pasŝlosilo ne estos kopiita al la klonaĵo. Ĉu vi volas daŭrigi la klonadon de ĉi tiu ero?" }, "modifiedCollectionManagement": { "message": "Modified collection management setting $ID$.", @@ -8792,13 +8792,13 @@ "description": "This is followed a by a hyperlink to the help website." }, "installBrowserExtension": { - "message": "Install browser extension" + "message": "Instali la retumilan etendaĵon" }, "installBrowserExtensionDetails": { - "message": "Use the extension to quickly save logins and auto-fill forms without opening the web app." + "message": "Uzu la etendaĵon por rapide konservi salutilojn kaj aŭtomate plenigi la kampojn sen malfermo de la reteja apo." }, "projectAccessUpdated": { - "message": "Project access updated" + "message": "Projekto-alirebleco ĝisdatiĝis" }, "unexpectedErrorSend": { "message": "An unexpected error has occurred while loading this Send. Try again later." @@ -8813,7 +8813,7 @@ "message": "Seat limit has been reached. Contact your provider to purchase additional seats." }, "collectionAccessRestricted": { - "message": "Collection access is restricted" + "message": "La aliro al kolekto estas limigita" }, "readOnlyCollectionAccess": { "message": "You do not have access to manage this collection." @@ -9057,15 +9057,15 @@ "message": "Machine accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, "machineAccount": { - "message": "Machine account", + "message": "Maŝina konto", "description": "A machine user which can be used to automate processes and access secrets in the system." }, "machineAccounts": { - "message": "Machine accounts", + "message": "Maŝinaj kontoj", "description": "The title for the section that deals with machine accounts." }, "newMachineAccount": { - "message": "New machine account", + "message": "Nova maŝina konto", "description": "Title for creating a new machine account." }, "machineAccountsNoItemsMessage": { @@ -9073,23 +9073,23 @@ "description": "Message to encourage the user to start creating machine accounts." }, "machineAccountsNoItemsTitle": { - "message": "Nothing to show yet", + "message": "Ankoraŭ neniu por montri", "description": "Title to indicate that there are no machine accounts to display." }, "deleteMachineAccounts": { - "message": "Delete machine accounts", + "message": "Forĵeti maŝinan konton", "description": "Title for the action to delete one or multiple machine accounts." }, "deleteMachineAccount": { - "message": "Delete machine account", + "message": "Forĵeti maŝinajn kontojn", "description": "Title for the action to delete a single machine account." }, "viewMachineAccount": { - "message": "View machine account", + "message": "Vidi maŝinan konton", "description": "Action to view the details of a machine account." }, "deleteMachineAccountDialogMessage": { - "message": "Deleting machine account $MACHINE_ACCOUNT$ is permanent and irreversible.", + "message": "Forĵeti la maŝinan konton $MACHINE_ACCOUNT$ estas poreterne kaj neinverseble.", "placeholders": { "machine_account": { "content": "$1", @@ -10102,7 +10102,7 @@ "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." }, "deleteOrganizationUser": { - "message": "Delete $NAME$", + "message": "Fo", "placeholders": { "name": { "content": "$1", @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index c53a464402e..a535b45e721 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Una vez actualices tu clave de cifrado, será necesario que cierres sesión y vuelvas a identificarte en todas las aplicaciones de Bitwarden que estés utilizando (como la aplicación móvil o la extensión de navegador). Si la reautenticación falla (la cual descargaría la nueva clave de cifrad) puede producirse corrupción de datos. Intentaremos cerrar tu sesión automáticamente, pero puede tardar un tiempo." }, - "updateEncryptionKeyExportWarning": { - "message": "Cualquier exportación cifrada que hayas guardado también será inválida." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Suscripción" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 3d71fbdf9e9..9ed4db4b0e3 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Pärast krüpteerimisvõtme uuendamist pead kõikides seadmetes, kus Bitwardeni rakendust kasutad, oma kontosse uuesti sisse logima (nt nutitelefonis ja brauseris). Välja- ja sisselogimise (mis ühtlasi laadib ka uue krüpteerimisvõtme) nurjumine võib tingida andmete riknemise. Üritame sinu seadmetest ise välja logida, aga see võib võtta natukene aega." }, - "updateEncryptionKeyExportWarning": { - "message": "Mistahes krüpteeritud, eksporditud andmed muutuvad samuti vigaseks." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Tellimus" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 7feb54a6adb..29f132852eb 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Zifratze-gakoa eguneratu ondoren, saioa hasi eta erabiltzen ari zaren Bitwarden aplikazio guztietara itzuli behar duzu (adibidez, aplikazio mugikorra edo nabigatzailearen gehigarriak). Saioa berriro hasteak huts egiten badu (zifratze-gako berria deskargatzea dakar) datuen korrupzioa ekar lezake. Automatikoki saioa ixten saiatuko gara, baina atzeratu egin daiteke." }, - "updateEncryptionKeyExportWarning": { - "message": "Gorde duzun edozein esportazio zifratu ez da baliozkoa izango." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Harpidetza" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index e2e939d1e40..2fbacc8dca2 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "پس از به‌روزرسانی کلید رمزگذاری، باید از سیستم خارج شوید و دوباره به همه برنامه‌های Bitwarden که در حال حاضر استفاده می‌کنید (مانند برنامه تلفن همراه یا برنامه‌های افزودنی مرورگر) وارد شوید. عدم خروج و ورود مجدد (که کلید رمزگذاری جدید شما را دانلود می‌کند) ممکن است منجر به خراب شدن داده‌ها شود. ما سعی خواهیم کرد شما را به طور خودکار از سیستم خارج کنیم، اما ممکن است با تأخیر انجام شود." }, - "updateEncryptionKeyExportWarning": { - "message": "هر برون ریزی رمزگذاری شده ای که ذخیره کرده اید نیز نامعتبر خواهد بود." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "اشتراک" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 041b54601e0..83efcc197fc 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Salausavaimesi päivityksen jälkeen, sinun tulee kirjautua ulos ja sitten takaisin sisään kaikissa Bitwarden-sovelluksissa, jotka ovat käytössäsi (esim. mobiilisovellus ja selainlaajennukset). Uudelleenkirjautumisen (joka lataa uuden salausavaimen) suorittamatta jättäminen saattaa johtaa tietojen vaurioitumiseen. Yritämme kirjata sinut ulos autmaattisesti, mutta tämä voi tapahtua vasta jonkin ajan kuluttua." }, - "updateEncryptionKeyExportWarning": { - "message": "Tallentamistasi salatuista vienneistä tulee käyttökelvottomia." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Tilaus" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 3cf1f3bcf7b..15e0793d6ce 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Matapos i update ang iyong key sa pag encrypt, kinakailangan kang mag log out at bumalik sa lahat ng mga application ng Bitwarden na kasalukuyang ginagamit mo (tulad ng mobile app o mga extension ng browser). Ang kabiguan na mag log out at bumalik sa (na nag download ng iyong bagong key ng pag encrypt) ay maaaring magresulta sa pagkasira ng data. Susubukan naming awtomatikong mag log out sa iyo, gayunpaman, maaari itong maantala." }, - "updateEncryptionKeyExportWarning": { - "message": "Ang anumang naka encrypt na pag export na iyong nai save ay magiging hindi wasto din." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subskripsyon" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 3374312e8cd..39fe5b3c1cf 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Après avoir mis à jour votre clé de chiffrement, vous devez vous déconnecter et vous reconnecter à toutes les applications Bitwarden que vous utilisez actuellement (comme l'application mobile ou les extensions de navigateur). Si vous ne vous déconnectez pas et ne vous reconnectez pas (ce qui télécharge votre nouvelle clé de chiffrement), cela peut entraîner une corruption des données. Nous tenterons de vous déconnecter automatiquement, mais cela peut être retardé." }, - "updateEncryptionKeyExportWarning": { - "message": "Tous les exports chiffrés que vous avez enregistrés deviendront également invalides." + "updateEncryptionKeyAccountExportWarning": { + "message": "Toutes les exportations restreintes du compte que vous avez enregistrées seront invalides." }, "subscription": { "message": "Abonnement" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Le paiement avec un compte bancaire est seulement disponible pour les clients aux États-Unis. Vous devrez procéder à la vérification de votre compte bancaire. Nous effectuerons un micro-dépôt dans les 1-2 prochains jours ouvrables. Entrez le code de la transaction de ce dépôt sur la page d'abonnement de votre fournisseur pour compléter la vérification du compte bancaire. Si vous ne complétez pas la vérification de votre compte bancaire résultera en un paiement manqué et votre abonnement sera suspendu." + }, + "clickPayWithPayPal": { + "message": "Veuillez cliquer sur le bouton Payer avec PayPal pour ajouter votre mode de paiement." } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index bfbd2983950..eae36865876 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index aa16586a707..286276f96e5 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "לאחר עדכון מפתחות ההצפנה שלך, תתבקש לצאת ולהכנס שוב בכל אפליקציות Bitwarden שאתה משתמש בהן (האפליקציה לפלאפון או ההרחבה לדפדפן). אם לא תצא ותכנס שוב (פעולת הכניסה מורידה את המפתח החדש), יתכן שתתקל במידע שגוי. אנו ננסה לגרום ליציאה אוטומטית, אך יתכן שהדבר לא יקרה מיידית." }, - "updateEncryptionKeyExportWarning": { - "message": "כל הייצואים המוצפנים ששמרת יהפכו גם הם ללא תקפים." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "מנוי" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index dbf2b839ad9..dd2073a924f 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 36ba847012b..4f1c4c981b2 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Nakon ažuriranja svojeg ključa za šifriranje, obavezno se trebaš odjaviti i ponovno prijaviti u sve Bitwarden aplikacije koje trenutno koristiš (npr. mobilna aplikacija, proširenje preglednika, ...). Ako se ne odjaviš i ponovno prijaviš (čime se preuzima tvoj novi ključ za šifriranje) može doći do oštećenja spremljenih podataka. Pokušati ćemo te automatski odjaviti, no, to bi možda moglo potrajati." }, - "updateEncryptionKeyExportWarning": { - "message": "Svi spremljeni šifrirani izvozi također će postati nevažeći." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Pretplata" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index e83e65e2ded..12fd95bbbaa 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "A titkosítási kulcs frissítése után ki kell jelentkezni és vissza kell jelentkezni az összes jelenleg használt Bitwarden alkalmazásba (például a mobilalkalmazás vagy a böngésző bővítmények). A kijelentkezés és a bejelentkezés elmulasztása (amely letölti az új titkosítási kulcsot) adatvesztést okozhat. Megkíséreljük az automatikusan kijelentkeztetést, azonban ez késhet." }, - "updateEncryptionKeyExportWarning": { - "message": "Az el nem mentett titkosított exportok szintén érvénytelenné válnak." + "updateEncryptionKeyAccountExportWarning": { + "message": "A fiókhoz korlátozottan mentett exportálások érvénytelenek lesznek." }, "subscription": { "message": "Előfizetés" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "A bankszámlával történő fizetés csak az Egyesült Államokban élő ügyfelek számára érhető el. A bankszámlát igazolni kell. A fejlesztők a következő 1-2 munkanapon belül mikrobetétet teljesítenek. A bankszámla ellenőrzéséhez írjuk be az utalás leíró kódját a szervezet számlázási oldalán. Ha elmulasztjuk a bankszámla igazolását, az a fizetés elmaradását és az előfizetés felfüggesztését vonja maga után." + }, + "clickPayWithPayPal": { + "message": "Kattintás a Pay with PayPal gombra a fizetési mód hozzáadásához." } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index f29ef18ed3b..3706fb73f63 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Setelah memperbarui kunci enkripsi Anda, Anda diminta untuk keluar dan masuk kembali ke semua aplikasi Bitwarden yang saat ini Anda gunakan (seperti aplikasi seluler atau ekstensi browser). Kegagalan untuk keluar dan masuk kembali (yang mengunduh kunci enkripsi baru Anda) dapat menyebabkan kerusakan data. Kami akan mencoba mengeluarkan Anda secara otomatis, namun, hal itu mungkin tertunda." }, - "updateEncryptionKeyExportWarning": { - "message": "Ekspor terenkripsi apa pun yang telah Anda simpan juga akan menjadi tidak valid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Langganan" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index b336a29df48..a30c1f81f93 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Dopo aver aggiornato la tua chiave di crittografia, devi uscire ed entrare di nuovo in tutte le app Bitwarden che stai usando (come l'app mobile o l'estensione del browser). Se non si esce e rientra (per scaricare la nuova chiave di crittografia) i dati della tua cassaforte potrebbero essere danneggiati. Cercheremo di farti uscire automaticamente, ma potrebbe esserci un ritardo." }, - "updateEncryptionKeyExportWarning": { - "message": "Anche le esportazioni crittografate che hai salvato non saranno più valide." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Abbonamento" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 3ec7d6a3ffe..1bb8bf364b0 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "暗号化キーの更新後は、モバイルアプリやブラウザ拡張機能など現在利用中のすべてのBitwardenアプリで再ログインが必要となります。再ログインしないと(新しい暗号化キーをダウンロードすると)データが破損する可能性があります。自動的にログアウトを試みますが、遅延することがあります。" }, - "updateEncryptionKeyExportWarning": { - "message": "保存済みの暗号化されたエクスポートも無効になります。" + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "契約" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 6ebe9759a29..72e9066806c 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 7a206aca2e3..38740c78cba 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index d4847a51bad..2758b022b64 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "ನಿಮ್ಮ ಎನ್‌ಕ್ರಿಪ್ಶನ್ ಕೀಲಿಯನ್ನು ನವೀಕರಿಸಿದ ನಂತರ, ನೀವು ಪ್ರಸ್ತುತ ಬಳಸುತ್ತಿರುವ (ಮೊಬೈಲ್ ಅಪ್ಲಿಕೇಶನ್ ಅಥವಾ ಬ್ರೌಸರ್ ವಿಸ್ತರಣೆಗಳಂತಹ) ಎಲ್ಲಾ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗೆ ನೀವು ಲಾಗ್ ಔಟ್ ಮತ್ತು ಬ್ಯಾಕ್ ಇನ್ ಮಾಡಬೇಕಾಗುತ್ತದೆ. ಲಾಗ್ and ಟ್ ಮಾಡಲು ಮತ್ತು ಹಿಂತಿರುಗಲು ವಿಫಲವಾದರೆ (ಅದು ನಿಮ್ಮ ಹೊಸ ಎನ್‌ಕ್ರಿಪ್ಶನ್ ಕೀಲಿಯನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡುತ್ತದೆ) ಡೇಟಾ ಭ್ರಷ್ಟಾಚಾರಕ್ಕೆ ಕಾರಣವಾಗಬಹುದು. ನಾವು ನಿಮ್ಮನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಲಾಗ್ ಔಟ್ ಮಾಡಲು ಪ್ರಯತ್ನಿಸುತ್ತೇವೆ, ಆದಾಗ್ಯೂ, ಇದು ವಿಳಂಬವಾಗಬಹುದು." }, - "updateEncryptionKeyExportWarning": { - "message": "ನೀವು ಉಳಿಸಿದ ಯಾವುದೇ ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ರಫ್ತು ಸಹ ಅಮಾನ್ಯವಾಗುತ್ತದೆ." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "ಚಂದಾದಾರಿಕೆ" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 24fc6dad11a..ee089584adf 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "암호화 키를 업데이트하고난 후 현재 사용 중인 모든 Bitwarden 애플리케이션(예. 모바일 앱 혹은 브라우저 확장 기능)에서 로그아웃 후 다시 로그인해야 합니다. 재로그인하지 않으면 (새 암호화 키를 다운로드받는 경우) 데이터 손실이 발생할 수 있습니다. 자동으로 로그아웃을 시도하지만 지연될 수 있습니다." }, - "updateEncryptionKeyExportWarning": { - "message": "이전에 암호화 상태로 내보내기하여 저장한 데이터도 무효화됩니다." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "구독" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 0e561cec5c6..61ff8651c72 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Pēc šifrēšanas atslēgas atjaunināšanas ir nepieciešams atteikties un tad pieteikties visās Bitwarden lietotnēs, kas pašreiz tiek izmantotas (piemēram, tālruņa lietotnē vai pārlūku paplašinājumā). Ja tas netiks darīts (tā tiek lejupielādēta jaunā šifrēšanas atslēga), dati var tikt bojāti. Tiks veikts automātisks atteikšanās mēģinājums, tomēr tas var notikt ar aizkavi." }, - "updateEncryptionKeyExportWarning": { - "message": "Arī katra šifrētā izguve, kas ir saglabāta, kļūs nederīga." + "updateEncryptionKeyAccountExportWarning": { + "message": "Jebkuras konta ierobežotās izguves, kas ir saglabātas, kļūs nederīgas." }, "subscription": { "message": "Abonements" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Apmaksa ar bankas kontu ir pieejama tikai klientiem Savienotajās Valstīs. Būs nepieciešams apliecināt savu bankas kontu. Mēs veiksim sīkiemaksu nākamās darba dienas vai divu laikā. Pēc tam šīs iemaksas uzdevuma aprakstā esošais kods būs jāievada pakalpojuma sniedzēja abonēšanas lapā, lai apliecinātu bankas kontu. Bankas konta apliecināšanas neveikšana beigsies ar nokavētu maksājumu un apturētu abonementu." + }, + "clickPayWithPayPal": { + "message": "Lūgums klikšķināt pogu \"Apmaksāt ar PayPal, lai pievienotu savu maksājumu veidu." } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index a447cc360b3..dd4ca0b94cf 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "സബ്സ്ക്രിപ്ഷൻ" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 7a206aca2e3..38740c78cba 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 7a206aca2e3..38740c78cba 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 8f019081d67..93df01f3440 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Etter å ha oppdatert krypteringsnøkkelen din, er du påkrevd å logge av og på på alle Bitwarden-appene og -programmene som du bruker for øyeblikket (deriblant mobilappen og nettleserutvidelsene). Å ikke logge av og på igjen (noe som vil laste ned din nye krypteringsnøkkel) kan føre til datakorrumpering. Vi vil forsøke å logge deg av automatisk, men det kan kanskje bli forsinket." }, - "updateEncryptionKeyExportWarning": { - "message": "Eventuelle krypterte eksporter som du har lagret blir også ugyldig." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Abonnement" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 579022bfb93..215ebaf1849 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 7939d444d32..9d192639377 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Na het bijwerken van je encryptiesleutel moet je je afmelden en weer aanmelden bij alle Bitwarden-applicaties die je gebruikt (zoals de mobiele app of browserextensies). Als je niet opnieuw inlogt (wat je nieuwe encryptiesleutel downloadt), kan dit gegevensbeschadiging tot gevolg hebben. We proberen je automatisch uit te loggen, maar het kan zijn dat dit met enige vertraging gebeurt." }, - "updateEncryptionKeyExportWarning": { - "message": "Elke versleutelde export die je hebt bewaard wordt onbruikbaar." + "updateEncryptionKeyAccountExportWarning": { + "message": "Alle account-beperkte bewaarde exports die je hebt opgeslagen worden ongeldig." }, "subscription": { "message": "Abonnement" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Betaling met een bankrekening is alleen beschikbaar voor klanten in de Verenigde Staten. Je moet je bankrekening verifiëren. We zullen binnen 1-2 werkdagen een microbetaling uitvoeren. Voer de code van het bankafschrift uit deze storting in op de factuurpagina van de provider om de bankrekening te verifiëren. Als de bankrekening niet wordt geverifieerd, wordt er een betaling gemist en wordt je abonnement opgeschort." + }, + "clickPayWithPayPal": { + "message": "Klik op \"Pay with PayPal\" voor het toevoegen van je betaalmethode." } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 8de39adc8c4..3c03b2e7cb2 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 7a206aca2e3..38740c78cba 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 5e8ac7f9fe3..ab471e892ef 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Po zaktualizowaniu klucza szyfrowania, musisz ponownie zalogować się do wszystkich aplikacji Bitwarden, z których obecnie korzystasz (na przykład aplikacje mobilne lub rozszerzenia przeglądarki). Niepowodzenie logowania (podczas którego pobierany jest nowy klucz szyfrowania) może spowodować uszkodzenie danych. Postaramy się wylogować Ciebie automatycznie, jednak może to chwilę potrwać." }, - "updateEncryptionKeyExportWarning": { - "message": "Wszystkie zaszyfrowane pliki eksportu, które wcześniej zapisałeś, staną się nieprawidłowe." + "updateEncryptionKeyAccountExportWarning": { + "message": "Wszystkie zapisane eksporty objęte ograniczeniami konta zostaną unieważnione." }, "subscription": { "message": "Subskrypcja" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Płatność za pomocą konta bankowego jest dostępna tylko dla klientów w Stanach Zjednoczonych. Będziesz musiał/musiała zweryfikować swoje konto bankowe. W ciągu najbliższych 1-2 dni roboczych dokonamy mikrowpłaty. Wprowadź kod deskryptora z tej wpłaty na stronie dostawcy subskrypcji, aby zweryfikować konto bankowe. Niezweryfikowanie konta bankowego spowoduje brak płatności i zawieszenie Twojej subskrypcji." + }, + "clickPayWithPayPal": { + "message": "Kliknij przycisk Zapłać za pomocą PayPal, aby dodać metodę płatności." } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index a3f5038d178..b3f6dd37a8c 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Depois de atualizar sua chave de criptografia, é necessário encerrar e iniciar a sessão em todos os aplicativos do Bitwarden que você está usando atualmente (como o aplicativo móvel ou as extensões do navegador). Não encerrar e iniciar sessão (que baixa sua nova chave de criptografia) pode resultar em corrupção de dados. Nós tentaremos desconectá-lo automaticamente, mas isso pode demorar um pouco." }, - "updateEncryptionKeyExportWarning": { - "message": "Quaisquer exportações criptografadas que você tenha salvo também se tornarão inválidas." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Assinatura" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 2a72ced6496..5f1e02c82e3 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Depois de atualizar a sua chave de encriptação, é necessário terminar sessão e voltar a iniciar em todas as aplicações Bitwarden que está a utilizar atualmente (como a aplicação móvel ou as extensões do navegador). A falha em terminar sessão e voltar a iniciar (que descarrega a sua nova chave de encriptação) pode resultar em corrupção de dados. Tentaremos terminar a sua sessão automaticamente, no entanto, pode demorar." }, - "updateEncryptionKeyExportWarning": { - "message": "Quaisquer exportações encriptadas que tenha guardado também se tornarão inválidas." + "updateEncryptionKeyAccountExportWarning": { + "message": "Todas as exportações com restrições de conta que tenha guardado tornar-se-ão inválidas." }, "subscription": { "message": "Subscrição" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "O pagamento com uma conta bancária só está disponível para clientes nos Estados Unidos. Ser-lhe-á pedido que verifique a sua conta bancária. Efetuaremos um micro-depósito nos próximos 1-2 dias úteis. Introduza o código descritor do extrato deste depósito na página de subscrição do fornecedor para verificar a conta bancária. A não verificação da conta bancária resultará na falta de pagamento e na suspensão da sua subscrição." + }, + "clickPayWithPayPal": { + "message": "Por favor, clique no botão Pagar com PayPal para adicionar o seu método de pagamento." } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 2c825332283..64eb05dad22 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -9,7 +9,7 @@ "message": "Aplicațiile critice" }, "noCriticalAppsAtRisk": { - "message": "No critical applications at risk" + "message": "Nicio aplicație critică în pericol" }, "accessIntelligence": { "message": "Access Intelligence" @@ -18,7 +18,7 @@ "message": "Risk Insights" }, "passwordRisk": { - "message": "Password Risk" + "message": "Risc Parola" }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." @@ -33,19 +33,19 @@ } }, "notifiedMembers": { - "message": "Notified members" + "message": "Membri notificați" }, "revokeMembers": { - "message": "Revoke members" + "message": "Revocare utilizatori" }, "restoreMembers": { - "message": "Restore members" + "message": "Restabilire utilizatori" }, "cannotRestoreAccessError": { - "message": "Cannot restore organization access" + "message": "Nu se poate restaura accesul organizației" }, "allApplicationsWithCount": { - "message": "All applications ($COUNT$)", + "message": "Toate aplicațiile ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -87,31 +87,31 @@ "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." }, "noCriticalAppsTitle": { - "message": "You haven't marked any applications as a Critical" + "message": "Nu ai marcat nicio aplicație drept critică" }, "noCriticalAppsDescription": { "message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords." }, "markCriticalApps": { - "message": "Mark critical apps" + "message": "Marchează aplicațiile critice" }, "markAppAsCritical": { - "message": "Mark app as critical" + "message": "Marchează aplicația drept critică" }, "applicationsMarkedAsCriticalSuccess": { - "message": "Applications marked as critical" + "message": "Aplicații marcate drept critice" }, "application": { - "message": "Application" + "message": "Aplicație" }, "atRiskPasswords": { "message": "At-risk passwords" }, "requestPasswordChange": { - "message": "Request password change" + "message": "Solicită modificarea parolei" }, "totalPasswords": { - "message": "Total passwords" + "message": "Total parole" }, "searchApps": { "message": "Search applications" @@ -168,13 +168,13 @@ } }, "totalMembers": { - "message": "Total members" + "message": "Total membri" }, "atRiskApplications": { - "message": "At-risk applications" + "message": "Aplicații cu risc" }, "totalApplications": { - "message": "Total applications" + "message": "Toate aplicațiile" }, "unmarkAsCriticalApp": { "message": "Unmark as critical app" @@ -220,10 +220,10 @@ "message": "Note" }, "privateNote": { - "message": "Private note" + "message": "Notă privată" }, "note": { - "message": "Note" + "message": "Notă" }, "customFields": { "message": "Câmpuri particularizate" @@ -232,22 +232,22 @@ "message": "Numele titularului cardului" }, "loginCredentials": { - "message": "Login credentials" + "message": "Date de logare" }, "personalDetails": { - "message": "Personal details" + "message": "Detalii personale" }, "identification": { - "message": "Identification" + "message": "Identificare" }, "contactInfo": { - "message": "Contact info" + "message": "Informații contact" }, "cardDetails": { - "message": "Card details" + "message": "Detalii card" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "Detalii $BRAND$", "placeholders": { "brand": { "content": "$1", @@ -256,16 +256,16 @@ } }, "itemHistory": { - "message": "Item history" + "message": "Istoricul articolului" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "Cheie de autentificare" }, "autofillOptions": { - "message": "Autofill options" + "message": "Opțiuni de completare automată" }, "websiteUri": { - "message": "Website (URI)" + "message": "Site web (URI)" }, "websiteUriCount": { "message": "Website (URI) $COUNT$", @@ -278,13 +278,13 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "Site web adăugat" }, "addWebsite": { - "message": "Add website" + "message": "Adăugare site" }, "deleteWebsite": { - "message": "Delete website" + "message": "Ștergere site" }, "defaultLabel": { "message": "Default ($VALUE$)", @@ -315,7 +315,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Completare automată la încărcarea paginii?" }, "number": { "message": "Număr card" @@ -330,7 +330,7 @@ "message": "Cod de securitate (CVV)" }, "securityCodeSlashCVV": { - "message": "Security code / CVV" + "message": "Cod de securitate (CVV)" }, "identityName": { "message": "Numele identității" @@ -408,10 +408,10 @@ "message": "Dr." }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Card expirat" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "Dacă l-ați reînnoit, actualizați informațiile cardului" }, "expirationMonth": { "message": "Luna expirării" @@ -457,10 +457,10 @@ "description": "This describes a field that is 'linked' (related) to another field." }, "fieldType": { - "message": "Field type" + "message": "Tipul câmpului" }, "fieldLabel": { - "message": "Field label" + "message": "Eticheta câmp" }, "remove": { "message": "Ștergere" @@ -473,7 +473,7 @@ "description": "This is the folder for uncategorized items" }, "selfOwnershipLabel": { - "message": "You", + "message": "Tu", "description": "Used as a label to indicate that the user is the owner of an item." }, "addFolder": { @@ -496,10 +496,10 @@ } }, "newFolder": { - "message": "New folder" + "message": "Folder nou" }, "folderName": { - "message": "Folder name" + "message": "Numele folderului" }, "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" @@ -551,7 +551,7 @@ "message": "Generare parolă" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Generare parolă" }, "checkPassword": { "message": "Verificați dacă parola a fost dezvăluită." @@ -606,11 +606,11 @@ "description": "Search Login type" }, "searchCard": { - "message": "Search cards", + "message": "Căutare carduri", "description": "Search Card type" }, "searchIdentity": { - "message": "Search identities", + "message": "Caută identități", "description": "Search Identity type" }, "searchSecureNote": { @@ -624,7 +624,7 @@ "message": "Caută în seiful meu" }, "searchOrganization": { - "message": "Cauta organizatie" + "message": "Caută organizație" }, "searchMembers": { "message": "Caută membri" @@ -654,7 +654,7 @@ "message": "Notă securizată" }, "typeSshKey": { - "message": "SSH key" + "message": "Cheie SSH" }, "typeLoginPlural": { "message": "Conectări" @@ -687,7 +687,7 @@ "message": "Numele complet" }, "address": { - "message": "Address" + "message": "Adresă" }, "address1": { "message": "Adresă 1" @@ -759,7 +759,7 @@ } }, "new": { - "message": "New", + "message": "Nou", "description": "for adding new items" }, "item": { @@ -850,22 +850,22 @@ "message": "Copy phone" }, "copyEmail": { - "message": "Copy email" + "message": "Copiere e-mail" }, "copyCompany": { - "message": "Copy company" + "message": "Copiază firma" }, "copySSN": { "message": "Copy Social Security number" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Copiați numărul pașaportului" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Copiați numărul de licență" }, "copyName": { - "message": "Copy name" + "message": "Copiați numele" }, "me": { "message": "Eu" @@ -886,7 +886,7 @@ "message": "Articolele seifului" }, "filter": { - "message": "Filter" + "message": "Filtru" }, "deleteSelected": { "message": "Ștergere selecție" @@ -998,37 +998,37 @@ "message": "Dosar șters" }, "editInfo": { - "message": "Edit info" + "message": "Editează info" }, "access": { - "message": "Access" + "message": "Acces" }, "accessLevel": { - "message": "Access level" + "message": "Nivel de acces" }, "accessing": { - "message": "Accessing" + "message": "Accesare" }, "loggedOut": { "message": "Deconectat" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Ai fost deconectat din contul tău." }, "loginExpired": { "message": "Sesiunea de autentificare a expirat." }, "restartRegistration": { - "message": "Restart registration" + "message": "Reporniți înregistrarea" }, "expiredLink": { - "message": "Expired link" + "message": "Link expirat" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "Vă rugăm să reporniți înregistrarea sau să încercați să vă conectați." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Este posibil să aveți deja un cont" }, "logOutConfirmation": { "message": "Sigur doriți să vă deconectați?" @@ -1046,7 +1046,7 @@ "message": "Nu" }, "location": { - "message": "Location" + "message": "Locație" }, "loginOrCreateNewAccount": { "message": "Autentificați-vă sau creați un cont nou pentru a accesa seiful dvs. securizat." @@ -1058,31 +1058,31 @@ "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Selectezi o altă opțiune?" }, "loginWithMasterPassword": { "message": "Autentificați-vă cu parola principală" }, "readingPasskeyLoading": { - "message": "Reading passkey..." + "message": "Citire parolă..." }, "readingPasskeyLoadingInfo": { - "message": "Keep this window open and follow prompts from your browser." + "message": "Păstrează această fereastră deschisă și urmează instrucțiunile din browser-ul tău." }, "useADifferentLogInMethod": { - "message": "Use a different log in method" + "message": "Folosiți o metodă diferită de autentificare" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Autentificare cu parolă" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Autentificare unică" }, "welcomeBack": { - "message": "Welcome back" + "message": "Bine ați revenit!" }, "invalidPasskeyPleaseTryAgain": { - "message": "Invalid Passkey. Please try again." + "message": "Parolă nevalidă. Vă rugăm să încercați din nou." }, "twoFactorForPasskeysNotSupportedOnClientUpdateToLogIn": { "message": "2FA for passkeys is not supported. Update the app to log in." @@ -1091,7 +1091,7 @@ "message": "Use a generated passkey that will automatically log you in without a password. Biometrics, like facial recognition or fingerprint, or another FIDO2 security method will verify your identity." }, "newPasskey": { - "message": "New passkey" + "message": "Cheie parolă nouă" }, "learnMoreAboutPasswordless": { "message": "Learn more about passwordless" @@ -1106,31 +1106,31 @@ "message": "Error creating passkey" }, "errorCreatingPasskeyInfo": { - "message": "There was a problem creating your passkey." + "message": "A apărut o eroare la crearea parolei." }, "passkeySuccessfullyCreated": { - "message": "Passkey successfully created!" + "message": "Parola a fost creata!" }, "customPasskeyNameInfo": { - "message": "Name your passkey to help you identify it." + "message": "Numiți cheia de acces pentru a vă ajuta să o identificați." }, "useForVaultEncryption": { - "message": "Use for vault encryption" + "message": "Utilizat pentru criptarea seifului" }, "useForVaultEncryptionInfo": { "message": "Log in and unlock on supported devices without your master password. Follow the prompts from your browser to finalize setup." }, "useForVaultEncryptionErrorReadingPasskey": { - "message": "Error reading passkey. Try again or uncheck this option." + "message": "Eroare la citirea parolei. Încercați din nou sau debifați această opțiune." }, "encryptionNotSupported": { - "message": "Encryption not supported" + "message": "Criptare neacceptată" }, "enablePasskeyEncryption": { - "message": "Set up encryption" + "message": "Configurare criptare" }, "usedForEncryption": { - "message": "Used for encryption" + "message": "Folosit pentru criptare" }, "loginWithPasskeyEnabled": { "message": "Log in with passkey turned on" @@ -1145,10 +1145,10 @@ } }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "Parolă eliminată" }, "removePasskey": { - "message": "Remove passkey" + "message": "Înlăturare parolă" }, "removePasskeyInfo": { "message": "If all passkeys are removed, you will be unable to log into new devices without your master password." @@ -1157,16 +1157,16 @@ "message": "Passkey limit reached. Remove a passkey to add another." }, "tryAgain": { - "message": "Try again" + "message": "Încercați din nou" }, "createAccount": { "message": "Creare cont" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Nou pe Bitwarden?" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Setați o parolă puternică" }, "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" @@ -1184,10 +1184,10 @@ "message": "Log in to Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "Introdu codul trimis la emailul tău" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "Introdu codul din aplicația de autentificare" }, "pressYourYubiKeyToAuthenticate": { "message": "Press your YubiKey to authenticate" @@ -1199,13 +1199,13 @@ "message": "The authentication session timed out. Please restart the login process." }, "verifyYourIdentity": { - "message": "Verify your Identity" + "message": "Verificați identitatea" }, "weDontRecognizeThisDevice": { - "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + "message": "Dispozitiv nerecunoscut. Introdu codul trimis pe email pentru a verifica identitatea." }, "continueLoggingIn": { - "message": "Continue logging in" + "message": "Continuă autentificarea" }, "whatIsADevice": { "message": "What is a device?" @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "După actualizarea cheii de criptare, trebuie să vă reconectați în toate aplicațiile Bitwarden pe care le utilizați în prezent (cum ar fi aplicația mobilă sau extensiile browserului). Faptul de a nu vă deconecta și reconecta (care descarcă noua cheie de criptare) poate duce la corupția datelor. Vom încerca să vă deconectăm automat, însă ar putea fi întârziat." }, - "updateEncryptionKeyExportWarning": { - "message": "Orice export criptat pe care l-ați salvat va deveni, de asemenea, nevalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Abonament" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index bf35c9a916a..aaccc3b2145 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "После обновления ключа шифрования необходимо выйти из всех приложений Bitwarden, которые вы используете (например, из мобильного приложения или расширения браузера). Если этого не сделать, могут повредиться данные (так как при выходе и последующем входе загружается ваш новый ключ шифрования). Мы попытаемся автоматически осуществить завершение ваших сессий, однако это может произойти с задержкой." }, - "updateEncryptionKeyExportWarning": { - "message": "Любые зашифрованные экспортированные данные, которые вы сохранили, также станут недействительными." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Подписка" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Оплата с помощью банковского счета доступна только для клиентов в США. Вам потребуется подтвердить свой банковский счет. Мы сделаем микродепозит в течение следующих 1-2 рабочих дней. Введите код дескриптора выписки из этого депозита на странице подписки провайдера для подтверждения банковского счета. Неподтверждение банковского счета приведет к пропуску платежа и приостановке подписки." + }, + "clickPayWithPayPal": { + "message": "Пожалуйста, нажмите кнопку \"Оплатить с помощью PayPal\", чтобы добавить свой способ оплаты." } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index a8ddb31d50a..2d270a54731 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 3192cdd9644..6bbf765ba78 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -99,7 +99,7 @@ "message": "Označiť aplikáciu ako kritickú" }, "applicationsMarkedAsCriticalSuccess": { - "message": "Applications marked as critical" + "message": "Aplikácie označené ako kritické" }, "application": { "message": "Aplikácia" @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Po aktualizácii šifrovacieho kľúča budete požiadaní o opätovné prihlásenie do všetkých Bitwarden aplikácii ktoré momentálne používate (napríklad mobilné aplikácie, alebo rozšírenia v prehliadači). Ak sa opätovne neprihlásite (touto operáciou sa stiahnu nové šifrovacie kľúče), mohlo by to viesť k poškodeniu uložených dát. Pokúsime sa odhlásiť vás automaticky, ale môže to chvíľu trvať." }, - "updateEncryptionKeyExportWarning": { - "message": "Všetky uložené šifrované exporty sa tiež stanú neplatnými." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Predplatné" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Platba prostredníctvom bankového účtu je dostupná len pre zákazníkov v Spojených Štátoch. Budete musieť overiť svoj bankový účet. V priebehu nasledujúcich 1-2 pracovných dní vykonáme mikro vklad. Na overenie bankového účtu zadajte kód popisu výpisu z tohto vkladu na fakturačnej stránke poskytovateľa. Neoverenie bankového účtu bude mať za následok neuskutočnenie platby a pozastavenie vášho predplatného." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index a3605be29ef..06c5339ad98 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Naročnina" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index e1099d2c5f8..2ca7e72e227 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Након ажурирања кључа за шифровање, мораћете да се одјавите и вратите у све Bitwarden апликације које тренутно користите (као што су мобилна апликација или додаци прегледача). Ако се не одјавите и поново пријавите (чиме се преузима ваш нови кључ за шифровање), може доћи до оштећења података. Покушаћемо аутоматски да се одјавимо, али може доћи до одлагања." }, - "updateEncryptionKeyExportWarning": { - "message": "Сваки шифровани извоз који сте сачували такође ће постати неважећи." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Претплата" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Плаћање са банковним рачуном доступно је само купцима у Сједињеним Државама. Од вас ће бити потребно да проверите свој банковни рачун. Направит ћемо микро депозит у наредних 1-2 радна дана. Унесите кôд за дескриптор изјаве са овог депозита на претплатничкој страници провајдера да бисте потврдили банковни рачун. Неуспех у верификацији банковног рачуна резултираће пропуштеним плаћањем и претплатом је суспендовано." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 13a974031cc..6b10fd30018 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 634a709762b..1ca059f95ec 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Efter att ha uppdaterat din krypteringsnyckel, måste du logga ut och in igen i alla Bitwarden-program som du använder (t.ex. mobilappen och webbläsartillägget). Att inte logga ut och in igen (vilket hämtar din nya krypteringsnyckel) kan resultera i datakorruption. Vi kommer försöka logga ut dig automatiskt, men det kan vara fördröjt." }, - "updateEncryptionKeyExportWarning": { - "message": "Alla krypterade exporter som du har sparat kommer också bli ogiltiga." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Prenumeration" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 7a206aca2e3..38740c78cba 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 4fff9ab837e..393442e42e5 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 3f82a6d6c15..c59f33bba45 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Şifreleme anahtarınızı güncelledikten sonra, şu anda kullanmakta olduğunuz tüm Bitwarden uygulamalarında (mobil uygulama veya tarayıcı uzantıları gibi) oturumunuzu kapatıp tekrar açmanız gerekir. Yeni şifreleme anahtarınızı indirme için oturumu kapatıp tekrar açmamamız verilerin bozulmasına neden olabilir. Oturumunuzu otomatik olarak kapatmaya çalışacağız, ancak bu gecikebilir." }, - "updateEncryptionKeyExportWarning": { - "message": "Şifrelenmiş dışa aktarmalarınız da geçersiz olacaktır." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Abonelik" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index cdeaacf5fb4..288e8e840a0 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "Після оновлення вашого ключа шифрування вам необхідно вийти з системи і потім виконати повторний вхід у всіх програмах Bitwarden, які ви використовуєте. Збій при виході та повторному вході може призвести до пошкодження даних. Ми спробуємо завершити ваші сеанси автоматично, однак, цей процес може відбутися із затримкою." }, - "updateEncryptionKeyExportWarning": { - "message": "Будь-які зашифровані експортування, які ви зберегли, також стануть недійсними." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Передплата" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Оплата з банківським рахунком доступна тільки для клієнтів у США. Вам необхідно буде підтвердити свій банківський рахунок. Ми здійснимо мікродепозит протягом наступних 1–2 робочих днів. Введіть код дескриптора з цього депозиту на сторінці передплати провайдера, щоб підтвердити банківський рахунок. Неможливість засвідчення банківського рахунку призведе до втрати платежу та припинення вашої передплати." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 7204365cb21..cee1bf8ebbd 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Gói" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index af785810c7f..9805eaac5fd 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -2836,7 +2836,7 @@ "message": "支付卡" }, "paypalClickSubmit": { - "message": "点击 PayPal 按钮登录您的 PayPal 账户,然后点击下面的提交按钮继续。" + "message": "选择 PayPal 按钮登录您的 PayPal 账户,然后点击下面的「提交」按钮继续。" }, "cancelSubscription": { "message": "取消订阅" @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "更新加密密钥后,您需要注销所有当前使用的 Bitwarden 应用程序(例如移动 App 或浏览器扩展)然后重新登录。注销或者重新登录(这将下载新的加密密钥)失败可能会导致数据损坏。我们会尝试自动为您注销,但可能会有所延迟。" }, - "updateEncryptionKeyExportWarning": { - "message": "您保存的任何已加密导出也将变为无效。" + "updateEncryptionKeyAccountExportWarning": { + "message": "您已保存的所有账户限制的导出文件将失效。" }, "subscription": { "message": "订阅" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "使用银行账户付款仅对美国用户开放。您将被要求验证您的银行账户。我们将在 1-2 个工作日内进行一笔小额转账,请在提供商的订阅页面输入该转账的对账单描述符代码以验证银行账户。验证银行账户失败将会错过支付,您的订阅将失效。" + }, + "clickPayWithPayPal": { + "message": "请点击「使用 PayPal 付款」按钮以添加您的付款方式。" } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 59e77927862..57dd4c8631f 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -4548,8 +4548,8 @@ "updateEncryptionKeyWarning": { "message": "更新加密金鑰後,您需要登出並重新登入目前使用的所有 Bitwarden 應用程式(如行動應用程式或瀏覽器擴充套件)。登出和重新登入(這會下載新的加密金鑰)失敗可能會導致資料損毀。我們將嘗試自動登出,但可能會有所延遲。" }, - "updateEncryptionKeyExportWarning": { - "message": "您儲存的任何已加密匯出的檔案也將變成無效。" + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "訂閱" @@ -10610,5 +10610,8 @@ }, "verifyProviderBankAccountWithStatementDescriptorWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + }, + "clickPayWithPayPal": { + "message": "Please click the Pay with PayPal button to add your payment method." } } From a02c230e4d9f16f93c295ae857e6618349338f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Sat, 17 May 2025 22:17:36 +0200 Subject: [PATCH 104/245] Pin rust toolchain (#14817) * Pin rust toolchain * Always install targets in build script * Delete installed toolchains --- .github/renovate.json5 | 6 +++++ .github/workflows/build-desktop.yml | 26 ++----------------- apps/desktop/desktop_native/Cargo.lock | 16 ++++++------ apps/desktop/desktop_native/build.js | 6 +++++ .../desktop_native/rust-toolchain.toml | 4 +++ 5 files changed, 26 insertions(+), 32 deletions(-) create mode 100644 apps/desktop/desktop_native/rust-toolchain.toml diff --git a/.github/renovate.json5 b/.github/renovate.json5 index d0066ddd7ba..12ae415c6a1 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -51,6 +51,12 @@ commitMessagePrefix: "[deps] BRE:", addLabels: ["hold"], }, + { + // Enable support for Rust toolchain updates. + matchManagers: ["custom.regex"], + matchDepNames: ["rust"], + commitMessageTopic: "Rust", + }, { // By default, we send patch updates to the Dependency Dashboard and do not generate a PR. // We want to generate PRs for a select number of dependencies to ensure we stay up to date on these. diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 86dc74f7351..fab0df693cb 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -428,12 +428,6 @@ jobs: - name: Set up environmentF run: choco install checksum --no-progress - - name: Rust - shell: pwsh - run: | - rustup target install i686-pc-windows-msvc - rustup target install aarch64-pc-windows-msvc - - name: Print environment run: | node --version @@ -681,10 +675,6 @@ jobs: - name: Set up Node-gyp run: python3 -m pip install setuptools - - name: Rust - shell: pwsh - run: rustup target install aarch64-apple-darwin - - name: Print environment run: | node --version @@ -890,10 +880,6 @@ jobs: - name: Set up Node-gyp run: python3 -m pip install setuptools - - name: Rust - shell: pwsh - run: rustup target install aarch64-apple-darwin - - name: Print environment run: | node --version @@ -1040,9 +1026,7 @@ jobs: - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/desktop/desktop_native - run: | - rustup target add aarch64-apple-darwin - node build.js cross-platform + run: node build.js cross-platform - name: Build if: steps.build-cache.outputs.cache-hit != 'true' @@ -1139,10 +1123,6 @@ jobs: - name: Set up Node-gyp run: python3 -m pip install setuptools - - name: Rust - shell: pwsh - run: rustup target install aarch64-apple-darwin - - name: Print environment run: | node --version @@ -1296,9 +1276,7 @@ jobs: - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/desktop/desktop_native - run: | - rustup target add aarch64-apple-darwin - node build.js cross-platform + run: node build.js cross-platform - name: Build if: steps.build-cache.outputs.cache-hit != 'true' diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 115947f1a81..f3db03f8639 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -889,7 +889,7 @@ dependencies = [ "ssh-encoding", "ssh-key", "sysinfo", - "thiserror 1.0.69", + "thiserror 2.0.12", "tokio", "tokio-stream", "tokio-util", @@ -931,7 +931,7 @@ dependencies = [ "cc", "core-foundation", "glob", - "thiserror 1.0.69", + "thiserror 2.0.12", "tokio", ] @@ -2480,7 +2480,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -2971,11 +2971,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -2991,9 +2991,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", diff --git a/apps/desktop/desktop_native/build.js b/apps/desktop/desktop_native/build.js index da61da15f9d..2edd0e89616 100644 --- a/apps/desktop/desktop_native/build.js +++ b/apps/desktop/desktop_native/build.js @@ -45,6 +45,10 @@ function buildProxyBin(target, release = true) { } } +function installTarget(target) { + child_process.execSync(`rustup target add ${target}`, { stdio: 'inherit', cwd: __dirname }); +} + if (!crossPlatform && !target) { console.log(`Building native modules in ${mode} mode for the native architecture`); buildNapiModule(false, mode === "release"); @@ -54,6 +58,7 @@ if (!crossPlatform && !target) { if (target) { console.log(`Building for target: ${target} in ${mode} mode`); + installTarget(target); buildNapiModule(target, mode === "release"); buildProxyBin(target, mode === "release"); return; @@ -70,6 +75,7 @@ if (process.platform === "linux") { } platformTargets.forEach(([target, _]) => { + installTarget(target); buildNapiModule(target); buildProxyBin(target); }); diff --git a/apps/desktop/desktop_native/rust-toolchain.toml b/apps/desktop/desktop_native/rust-toolchain.toml new file mode 100644 index 00000000000..898a61f3f4b --- /dev/null +++ b/apps/desktop/desktop_native/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.85.0" +components = [ "rustfmt", "clippy" ] +profile = "minimal" From e73f902aee23f2576e2b0409df2bd8a04ac76423 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Mon, 19 May 2025 08:51:46 +0200 Subject: [PATCH 105/245] [PM-18576] Fix missing user id on remove password (#13777) * Passed in userId on RemovePasswordComponent. * Added userId on other references to KeyConnectorService methods * remove password component refactor, test coverage, enabled strict * explicit user id provided to key connector service * redirect to / instead when user not logged in or not managing organization * key connector service explicit user id * key connector service no longer requires account service * key connector service missing null type * cli convert to key connector unit tests * remove unnecessary SyncService * error toast not showing on ErrorResponse * bad import due to merge conflict * bad import due to merge conflict * missing loading in remove password component for browser extension * error handling in remove password component * organization observable race condition in key-connector * usesKeyConnector always returns boolean * unit test coverage * key connector reactive * reactive key connector service * introducing convertAccountRequired$ * cli build fix * moving message sending side effect to sync * key connector service unit tests * fix unit tests * unit tests in wrong place after KM code ownership move * infinite page reload * failing unit tests * failing unit tests --------- Co-authored-by: Todd Martin --- .../remove-password.component.html | 16 +- apps/cli/src/auth/commands/unlock.command.ts | 5 +- apps/cli/src/base-program.ts | 1 - .../convert-to-key-connector.command.spec.ts | 140 ++++++++ .../convert-to-key-connector.command.ts | 12 +- .../src/models/response/message.response.ts | 4 +- apps/cli/src/oss-serve-configurator.ts | 1 - apps/cli/src/program.ts | 1 - .../remove-password.component.html | 14 +- .../src/auth/guards/auth.guard.spec.ts | 6 +- libs/angular/src/auth/guards/auth.guard.ts | 2 +- .../abstractions/key-connector.service.ts | 29 +- .../services/key-connector.service.spec.ts | 338 +++++++++++++----- .../services/key-connector.service.ts | 104 +++--- .../src/platform/sync/default-sync.service.ts | 7 +- .../remove-password.component.spec.ts | 252 +++++++++++++ .../remove-password.component.ts | 95 +++-- 17 files changed, 782 insertions(+), 245 deletions(-) create mode 100644 apps/cli/src/key-management/convert-to-key-connector.command.spec.ts create mode 100644 libs/key-management-ui/src/key-connector/remove-password.component.spec.ts diff --git a/apps/browser/src/key-management/key-connector/remove-password.component.html b/apps/browser/src/key-management/key-connector/remove-password.component.html index 793bcff3e09..272f727a7bb 100644 --- a/apps/browser/src/key-management/key-connector/remove-password.component.html +++ b/apps/browser/src/key-management/key-connector/remove-password.component.html @@ -8,17 +8,17 @@
-
+
+
+ +
+
+

{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}

- - diff --git a/libs/angular/src/auth/guards/auth.guard.spec.ts b/libs/angular/src/auth/guards/auth.guard.spec.ts index 4ed72baf284..db08553749a 100644 --- a/libs/angular/src/auth/guards/auth.guard.spec.ts +++ b/libs/angular/src/auth/guards/auth.guard.spec.ts @@ -2,7 +2,7 @@ import { TestBed } from "@angular/core/testing"; import { Router } from "@angular/router"; import { RouterTestingModule } from "@angular/router/testing"; import { MockProxy, mock } from "jest-mock-extended"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { EmptyComponent } from "@bitwarden/angular/platform/guard/feature-flag.guard.spec"; import { @@ -30,9 +30,7 @@ describe("AuthGuard", () => { authService.getAuthStatus.mockResolvedValue(authStatus); const messagingService: MockProxy = mock(); const keyConnectorService: MockProxy = mock(); - keyConnectorService.getConvertAccountRequired.mockResolvedValue( - keyConnectorServiceRequiresAccountConversion, - ); + keyConnectorService.convertAccountRequired$ = of(keyConnectorServiceRequiresAccountConversion); const accountService: MockProxy = mock(); const activeAccountSubject = new BehaviorSubject(null); accountService.activeAccount$ = activeAccountSubject; diff --git a/libs/angular/src/auth/guards/auth.guard.ts b/libs/angular/src/auth/guards/auth.guard.ts index 329e365e542..690db37d090 100644 --- a/libs/angular/src/auth/guards/auth.guard.ts +++ b/libs/angular/src/auth/guards/auth.guard.ts @@ -47,7 +47,7 @@ export const authGuard: CanActivateFn = async ( if ( !routerState.url.includes("remove-password") && - (await keyConnectorService.getConvertAccountRequired()) + (await firstValueFrom(keyConnectorService.convertAccountRequired$)) ) { return router.createUrlTree(["/remove-password"]); } diff --git a/libs/common/src/key-management/key-connector/abstractions/key-connector.service.ts b/libs/common/src/key-management/key-connector/abstractions/key-connector.service.ts index b88f7bc7cd6..131b941f274 100644 --- a/libs/common/src/key-management/key-connector/abstractions/key-connector.service.ts +++ b/libs/common/src/key-management/key-connector/abstractions/key-connector.service.ts @@ -1,22 +1,25 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore +import { Observable } from "rxjs"; + import { Organization } from "../../../admin-console/models/domain/organization"; import { IdentityTokenResponse } from "../../../auth/models/response/identity-token.response"; import { UserId } from "../../../types/guid"; export abstract class KeyConnectorService { - setMasterKeyFromUrl: (url: string, userId: UserId) => Promise; - getManagingOrganization: (userId?: UserId) => Promise; - getUsesKeyConnector: (userId: UserId) => Promise; - migrateUser: (userId?: UserId) => Promise; - userNeedsMigration: (userId: UserId) => Promise; - convertNewSsoUserToKeyConnector: ( + abstract setMasterKeyFromUrl(url: string, userId: UserId): Promise; + + abstract getManagingOrganization(userId: UserId): Promise; + + abstract getUsesKeyConnector(userId: UserId): Promise; + + abstract migrateUser(userId: UserId): Promise; + + abstract convertNewSsoUserToKeyConnector( tokenResponse: IdentityTokenResponse, orgId: string, userId: UserId, - ) => Promise; - setUsesKeyConnector: (enabled: boolean, userId: UserId) => Promise; - setConvertAccountRequired: (status: boolean, userId?: UserId) => Promise; - getConvertAccountRequired: () => Promise; - removeConvertAccountRequired: (userId?: UserId) => Promise; + ): Promise; + + abstract setUsesKeyConnector(enabled: boolean, userId: UserId): Promise; + + abstract convertAccountRequired$: Observable; } diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts index eff3fc7f47c..a2e994ad9e4 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts @@ -1,7 +1,8 @@ import { mock } from "jest-mock-extended"; -import { of } from "rxjs"; +import { firstValueFrom, of, timeout, TimeoutError } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; import { KeyService } from "@bitwarden/key-management"; import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; @@ -20,11 +21,7 @@ import { MasterKey } from "../../../types/key"; import { FakeMasterPasswordService } from "../../master-password/services/fake-master-password.service"; import { KeyConnectorUserKeyRequest } from "../models/key-connector-user-key.request"; -import { - USES_KEY_CONNECTOR, - CONVERT_ACCOUNT_TO_KEY_CONNECTOR, - KeyConnectorService, -} from "./key-connector.service"; +import { USES_KEY_CONNECTOR, KeyConnectorService } from "./key-connector.service"; describe("KeyConnectorService", () => { let keyConnectorService: KeyConnectorService; @@ -73,32 +70,87 @@ describe("KeyConnectorService", () => { expect(keyConnectorService).not.toBeFalsy(); }); - describe("setUsesKeyConnector()", () => { - it("should update the usesKeyConnectorState with the provided value", async () => { - const state = stateProvider.activeUser.getFake(USES_KEY_CONNECTOR); + describe("setUsesKeyConnector", () => { + it("should update the usesKeyConnectorState with the provided false value", async () => { + const state = stateProvider.singleUser.getFake(mockUserId, USES_KEY_CONNECTOR); state.nextState(false); - const newValue = true; + await keyConnectorService.setUsesKeyConnector(true, mockUserId); - await keyConnectorService.setUsesKeyConnector(newValue, mockUserId); + expect(await firstValueFrom(state.state$)).toBe(true); + }); - expect(await keyConnectorService.getUsesKeyConnector(mockUserId)).toBe(newValue); + it("should update the usesKeyConnectorState with the provided true value", async () => { + const state = stateProvider.singleUser.getFake(mockUserId, USES_KEY_CONNECTOR); + state.nextState(true); + + await keyConnectorService.setUsesKeyConnector(false, mockUserId); + + expect(await firstValueFrom(state.state$)).toBe(false); }); }); - describe("getManagingOrganization()", () => { + describe("getUsesKeyConnector", () => { + it("should return false when uses key connector state is not set", async () => { + const state = stateProvider.singleUser.getFake(mockUserId, USES_KEY_CONNECTOR); + state.nextState(null); + + const usesKeyConnector = await keyConnectorService.getUsesKeyConnector(mockUserId); + + expect(usesKeyConnector).toEqual(false); + }); + + it("should return false when uses key connector state is set to false", async () => { + stateProvider.getUserState$(USES_KEY_CONNECTOR, mockUserId); + const state = stateProvider.singleUser.getFake(mockUserId, USES_KEY_CONNECTOR); + state.nextState(false); + + const usesKeyConnector = await keyConnectorService.getUsesKeyConnector(mockUserId); + + expect(usesKeyConnector).toEqual(false); + }); + + it("should return true when uses key connector state is set to true", async () => { + const state = stateProvider.singleUser.getFake(mockUserId, USES_KEY_CONNECTOR); + state.nextState(true); + + const usesKeyConnector = await keyConnectorService.getUsesKeyConnector(mockUserId); + + expect(usesKeyConnector).toEqual(true); + }); + }); + + describe("getManagingOrganization", () => { it("should return the managing organization with key connector enabled", async () => { // Arrange const orgs = [ - organizationData(true, true, "https://key-connector-url.com", 2, false), - organizationData(false, true, "https://key-connector-url.com", 2, false), - organizationData(true, false, "https://key-connector-url.com", 2, false), - organizationData(true, true, "https://other-url.com", 2, false), + organizationData( + true, + true, + "https://key-connector-url.com", + OrganizationUserType.User, + false, + ), + organizationData( + false, + true, + "https://key-connector-url.com", + OrganizationUserType.User, + false, + ), + organizationData( + true, + false, + "https://key-connector-url.com", + OrganizationUserType.User, + false, + ), + organizationData(true, true, "https://other-url.com", OrganizationUserType.User, false), ]; organizationService.organizations$.mockReturnValue(of(orgs)); // Act - const result = await keyConnectorService.getManagingOrganization(); + const result = await keyConnectorService.getManagingOrganization(mockUserId); // Assert expect(result).toEqual(orgs[0]); @@ -107,13 +159,25 @@ describe("KeyConnectorService", () => { it("should return undefined if no managing organization with key connector enabled is found", async () => { // Arrange const orgs = [ - organizationData(true, false, "https://key-connector-url.com", 2, false), - organizationData(false, false, "https://key-connector-url.com", 2, false), + organizationData( + true, + false, + "https://key-connector-url.com", + OrganizationUserType.User, + false, + ), + organizationData( + false, + false, + "https://key-connector-url.com", + OrganizationUserType.User, + false, + ), ]; organizationService.organizations$.mockReturnValue(of(orgs)); // Act - const result = await keyConnectorService.getManagingOrganization(); + const result = await keyConnectorService.getManagingOrganization(mockUserId); // Assert expect(result).toBeUndefined(); @@ -128,7 +192,7 @@ describe("KeyConnectorService", () => { organizationService.organizations$.mockReturnValue(of(orgs)); // Act - const result = await keyConnectorService.getManagingOrganization(); + const result = await keyConnectorService.getManagingOrganization(mockUserId); // Assert expect(result).toBeUndefined(); @@ -137,74 +201,31 @@ describe("KeyConnectorService", () => { it("should return undefined if user is a Provider", async () => { // Arrange const orgs = [ - organizationData(true, true, "https://key-connector-url.com", 2, true), - organizationData(false, true, "https://key-connector-url.com", 2, true), + organizationData( + true, + true, + "https://key-connector-url.com", + OrganizationUserType.User, + true, + ), + organizationData( + false, + true, + "https://key-connector-url.com", + OrganizationUserType.User, + true, + ), ]; organizationService.organizations$.mockReturnValue(of(orgs)); // Act - const result = await keyConnectorService.getManagingOrganization(); + const result = await keyConnectorService.getManagingOrganization(mockUserId); // Assert expect(result).toBeUndefined(); }); }); - describe("setConvertAccountRequired()", () => { - it("should update the convertAccountToKeyConnectorState with the provided value", async () => { - const state = stateProvider.activeUser.getFake(CONVERT_ACCOUNT_TO_KEY_CONNECTOR); - state.nextState(false); - - const newValue = true; - - await keyConnectorService.setConvertAccountRequired(newValue); - - expect(await keyConnectorService.getConvertAccountRequired()).toBe(newValue); - }); - - it("should remove the convertAccountToKeyConnectorState", async () => { - const state = stateProvider.activeUser.getFake(CONVERT_ACCOUNT_TO_KEY_CONNECTOR); - state.nextState(false); - - const newValue: boolean = null; - - await keyConnectorService.setConvertAccountRequired(newValue); - - expect(await keyConnectorService.getConvertAccountRequired()).toBe(newValue); - }); - }); - - describe("userNeedsMigration()", () => { - it("should return true if the user needs migration", async () => { - // token - tokenService.getIsExternal.mockResolvedValue(true); - - // create organization object - const data = organizationData(true, true, "https://key-connector-url.com", 2, false); - organizationService.organizations$.mockReturnValue(of([data])); - - // uses KeyConnector - const state = stateProvider.activeUser.getFake(USES_KEY_CONNECTOR); - state.nextState(false); - - const result = await keyConnectorService.userNeedsMigration(mockUserId); - - expect(result).toBe(true); - }); - - it("should return false if the user does not need migration", async () => { - tokenService.getIsExternal.mockResolvedValue(false); - const data = organizationData(false, false, "https://key-connector-url.com", 2, false); - organizationService.organizations$.mockReturnValue(of([data])); - - const state = stateProvider.activeUser.getFake(USES_KEY_CONNECTOR); - state.nextState(true); - const result = await keyConnectorService.userNeedsMigration(mockUserId); - - expect(result).toBe(false); - }); - }); - describe("setMasterKeyFromUrl", () => { it("should set the master key from the provided URL", async () => { // Arrange @@ -221,10 +242,7 @@ describe("KeyConnectorService", () => { // Assert expect(apiService.getMasterKeyFromKeyConnector).toHaveBeenCalledWith(url); - expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith( - masterKey, - expect.any(String), - ); + expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(masterKey, mockUserId); }); it("should handle errors thrown during the process", async () => { @@ -246,10 +264,16 @@ describe("KeyConnectorService", () => { }); }); - describe("migrateUser()", () => { + describe("migrateUser", () => { it("should migrate the user to the key connector", async () => { // Arrange - const organization = organizationData(true, true, "https://key-connector-url.com", 2, false); + const organization = organizationData( + true, + true, + "https://key-connector-url.com", + OrganizationUserType.User, + false, + ); const masterKey = getMockMasterKey(); masterPasswordService.masterKeySubject.next(masterKey); const keyConnectorRequest = new KeyConnectorUserKeyRequest( @@ -260,7 +284,7 @@ describe("KeyConnectorService", () => { jest.spyOn(apiService, "postUserKeyToKeyConnector").mockResolvedValue(); // Act - await keyConnectorService.migrateUser(); + await keyConnectorService.migrateUser(mockUserId); // Assert expect(keyConnectorService.getManagingOrganization).toHaveBeenCalled(); @@ -273,7 +297,13 @@ describe("KeyConnectorService", () => { it("should handle errors thrown during migration", async () => { // Arrange - const organization = organizationData(true, true, "https://key-connector-url.com", 2, false); + const organization = organizationData( + true, + true, + "https://key-connector-url.com", + OrganizationUserType.User, + false, + ); const masterKey = getMockMasterKey(); const keyConnectorRequest = new KeyConnectorUserKeyRequest( Utils.fromBufferToB64(masterKey.inner().encryptionKey), @@ -288,7 +318,7 @@ describe("KeyConnectorService", () => { try { // Act - await keyConnectorService.migrateUser(); + await keyConnectorService.migrateUser(mockUserId); } catch { // Assert expect(logService.error).toHaveBeenCalledWith(error); @@ -301,6 +331,136 @@ describe("KeyConnectorService", () => { }); }); + describe("convertAccountRequired$", () => { + beforeEach(async () => { + const organization = organizationData( + true, + true, + "https://key-connector-url.com", + OrganizationUserType.User, + false, + ); + organizationService.organizations$.mockReturnValue(of([organization])); + await stateProvider.getUser(mockUserId, USES_KEY_CONNECTOR).update(() => false); + tokenService.getIsExternal.mockResolvedValue(true); + tokenService.hasAccessToken$.mockReturnValue(of(true)); + }); + + it("should return true when user logged in with sso, belong to organization using key connector and user does not use key connector", async () => { + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).resolves.toEqual(true); + }); + + it("should return false when user logged in with password", async () => { + tokenService.getIsExternal.mockResolvedValue(false); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).resolves.toEqual(false); + }); + + it("should return false when organization's key connector disabled", async () => { + const organization = organizationData( + true, + false, + "https://key-connector-url.com", + OrganizationUserType.User, + false, + ); + organizationService.organizations$.mockReturnValue(of([organization])); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).resolves.toEqual(false); + }); + + it("should return false when user is admin of the organization", async () => { + const organization = organizationData( + true, + true, + "https://key-connector-url.com", + OrganizationUserType.Admin, + false, + ); + organizationService.organizations$.mockReturnValue(of([organization])); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).resolves.toEqual(false); + }); + + it("should return false when user is owner of the organization", async () => { + const organization = organizationData( + true, + true, + "https://key-connector-url.com", + OrganizationUserType.Owner, + false, + ); + organizationService.organizations$.mockReturnValue(of([organization])); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).resolves.toEqual(false); + }); + + it("should return false when user is provider user of the organization", async () => { + const organization = organizationData( + true, + true, + "https://key-connector-url.com", + OrganizationUserType.User, + true, + ); + organizationService.organizations$.mockReturnValue(of([organization])); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).resolves.toEqual(false); + }); + + it("should return false when user already uses key connector", async () => { + await stateProvider.getUser(mockUserId, USES_KEY_CONNECTOR).update(() => true); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).resolves.toEqual(false); + }); + + it("should not return any value when user not logged in", async () => { + await accountService.switchAccount(null as unknown as UserId); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).rejects.toBeInstanceOf(TimeoutError); + }); + + it("should not return any value when organization state is empty", async () => { + organizationService.organizations$.mockReturnValue(of(null as unknown as Organization[])); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).rejects.toBeInstanceOf(TimeoutError); + }); + + it("should not return any value when user is not using key connector", async () => { + await stateProvider.getUser(mockUserId, USES_KEY_CONNECTOR).update(() => null); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).rejects.toBeInstanceOf(TimeoutError); + }); + + it("should not return any value when user does not have access token", async () => { + tokenService.hasAccessToken$.mockReturnValue(of(false)); + + await expect( + firstValueFrom(keyConnectorService.convertAccountRequired$.pipe(timeout(100))), + ).rejects.toBeInstanceOf(TimeoutError); + }); + }); + function organizationData( usesKeyConnector: boolean, keyConnectorEnabled: boolean, diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.ts index 9799f06f64a..744b7d6608c 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.ts @@ -1,8 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom } from "rxjs"; +import { combineLatest, filter, firstValueFrom, Observable, of, switchMap } from "rxjs"; import { LogoutReason } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { Argon2KdfConfig, KdfConfig, @@ -15,7 +16,6 @@ import { ApiService } from "../../../abstractions/api.service"; import { OrganizationService } from "../../../admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserType } from "../../../admin-console/enums"; import { Organization } from "../../../admin-console/models/domain/organization"; -import { AccountService } from "../../../auth/abstractions/account.service"; import { TokenService } from "../../../auth/abstractions/token.service"; import { IdentityTokenResponse } from "../../../auth/models/response/identity-token.response"; import { KeysRequest } from "../../../models/request/keys.request"; @@ -23,12 +23,7 @@ import { KeyGenerationService } from "../../../platform/abstractions/key-generat import { LogService } from "../../../platform/abstractions/log.service"; import { Utils } from "../../../platform/misc/utils"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; -import { - ActiveUserState, - KEY_CONNECTOR_DISK, - StateProvider, - UserKeyDefinition, -} from "../../../platform/state"; +import { KEY_CONNECTOR_DISK, StateProvider, UserKeyDefinition } from "../../../platform/state"; import { UserId } from "../../../types/guid"; import { MasterKey } from "../../../types/key"; import { InternalMasterPasswordServiceAbstraction } from "../../master-password/abstractions/master-password.service.abstraction"; @@ -42,23 +37,15 @@ export const USES_KEY_CONNECTOR = new UserKeyDefinition( { deserializer: (usesKeyConnector) => usesKeyConnector, clearOn: ["logout"], - }, -); - -export const CONVERT_ACCOUNT_TO_KEY_CONNECTOR = new UserKeyDefinition( - KEY_CONNECTOR_DISK, - "convertAccountToKeyConnector", - { - deserializer: (convertAccountToKeyConnector) => convertAccountToKeyConnector, - clearOn: ["logout"], + cleanupDelayMs: 0, }, ); export class KeyConnectorService implements KeyConnectorServiceAbstraction { - private usesKeyConnectorState: ActiveUserState; - private convertAccountToKeyConnectorState: ActiveUserState; + readonly convertAccountRequired$: Observable; + constructor( - private accountService: AccountService, + accountService: AccountService, private masterPasswordService: InternalMasterPasswordServiceAbstraction, private keyService: KeyService, private apiService: ApiService, @@ -69,9 +56,27 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { private logoutCallback: (logoutReason: LogoutReason, userId?: string) => Promise, private stateProvider: StateProvider, ) { - this.usesKeyConnectorState = this.stateProvider.getActive(USES_KEY_CONNECTOR); - this.convertAccountToKeyConnectorState = this.stateProvider.getActive( - CONVERT_ACCOUNT_TO_KEY_CONNECTOR, + this.convertAccountRequired$ = accountService.activeAccount$.pipe( + filter((account) => account != null), + switchMap((account) => + combineLatest([ + of(account.id), + this.organizationService + .organizations$(account.id) + .pipe(filter((organizations) => organizations != null)), + this.stateProvider + .getUserState$(USES_KEY_CONNECTOR, account.id) + .pipe(filter((usesKeyConnector) => usesKeyConnector != null)), + tokenService.hasAccessToken$(account.id).pipe(filter((hasToken) => hasToken)), + ]), + ), + switchMap(async ([userId, organizations, usesKeyConnector]) => { + const loggedInUsingSso = await this.tokenService.getIsExternal(userId); + const requiredByOrganization = this.findManagingOrganization(organizations) != null; + const userIsNotUsingKeyConnector = !usesKeyConnector; + + return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector; + }), ); } @@ -79,20 +84,13 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { await this.stateProvider.getUser(userId, USES_KEY_CONNECTOR).update(() => usesKeyConnector); } - getUsesKeyConnector(userId: UserId): Promise { - return firstValueFrom(this.stateProvider.getUserState$(USES_KEY_CONNECTOR, userId)); + async getUsesKeyConnector(userId: UserId): Promise { + return ( + (await firstValueFrom(this.stateProvider.getUserState$(USES_KEY_CONNECTOR, userId))) ?? false + ); } - async userNeedsMigration(userId: UserId) { - const loggedInUsingSso = await this.tokenService.getIsExternal(userId); - const requiredByOrganization = (await this.getManagingOrganization(userId)) != null; - const userIsNotUsingKeyConnector = !(await this.getUsesKeyConnector(userId)); - - return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector; - } - - async migrateUser(userId?: UserId) { - userId ??= (await firstValueFrom(this.accountService.activeAccount$))?.id; + async migrateUser(userId: UserId) { const organization = await this.getManagingOrganization(userId); const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId)); const keyConnectorRequest = new KeyConnectorUserKeyRequest( @@ -109,6 +107,8 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { } await this.apiService.postConvertToKeyConnector(); + + await this.setUsesKeyConnector(true, userId); } // TODO: UserKey should be renamed to MasterKey and typed accordingly @@ -123,15 +123,9 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { } } - async getManagingOrganization(userId?: UserId): Promise { - const orgs = await firstValueFrom(this.organizationService.organizations$(userId)); - return orgs.find( - (o) => - o.keyConnectorEnabled && - o.type !== OrganizationUserType.Admin && - o.type !== OrganizationUserType.Owner && - !o.isProviderUser, - ); + async getManagingOrganization(userId: UserId): Promise { + const organizations = await firstValueFrom(this.organizationService.organizations$(userId)); + return this.findManagingOrganization(organizations); } async convertNewSsoUserToKeyConnector( @@ -188,18 +182,6 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { await this.apiService.postSetKeyConnectorKey(setPasswordRequest); } - async setConvertAccountRequired(status: boolean, userId?: UserId) { - await this.stateProvider.setUserState(CONVERT_ACCOUNT_TO_KEY_CONNECTOR, status, userId); - } - - getConvertAccountRequired(): Promise { - return firstValueFrom(this.convertAccountToKeyConnectorState.state$); - } - - async removeConvertAccountRequired(userId?: UserId) { - await this.setConvertAccountRequired(null, userId); - } - private handleKeyConnectorError(e: any) { this.logService.error(e); if (this.logoutCallback != null) { @@ -209,4 +191,14 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { } throw new Error("Key Connector error"); } + + private findManagingOrganization(organizations: Organization[]) { + return organizations.find( + (o) => + o.keyConnectorEnabled && + o.type !== OrganizationUserType.Admin && + o.type !== OrganizationUserType.Owner && + !o.isProviderUser, + ); + } } diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index faf54f11912..e9f6c60af64 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -224,13 +224,8 @@ export class DefaultSyncService extends CoreSyncService { await this.syncProfileOrganizations(response, response.id); - if (await this.keyConnectorService.userNeedsMigration(response.id)) { - await this.keyConnectorService.setConvertAccountRequired(true, response.id); + if (await firstValueFrom(this.keyConnectorService.convertAccountRequired$)) { this.messageSender.send("convertAccountToKeyConnector"); - } else { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.keyConnectorService.removeConvertAccountRequired(response.id); } } diff --git a/libs/key-management-ui/src/key-connector/remove-password.component.spec.ts b/libs/key-management-ui/src/key-connector/remove-password.component.spec.ts new file mode 100644 index 00000000000..a3ab70a6184 --- /dev/null +++ b/libs/key-management-ui/src/key-connector/remove-password.component.spec.ts @@ -0,0 +1,252 @@ +import { Router } from "@angular/router"; +import { mock } from "jest-mock-extended"; + +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { SyncService } from "@bitwarden/common/platform/sync"; +import { UserId } from "@bitwarden/common/types/guid"; +import { DialogService, ToastService } from "@bitwarden/components"; + +import { mockAccountServiceWith } from "../../../common/spec"; + +import { RemovePasswordComponent } from "./remove-password.component"; + +describe("RemovePasswordComponent", () => { + let component: RemovePasswordComponent; + + const userId = "test-user-id" as UserId; + const organization = { + id: "test-organization-id", + name: "test-organization-name", + } as Organization; + + const accountService = mockAccountServiceWith(userId); + + const mockRouter = mock(); + const mockSyncService = mock(); + const mockI18nService = mock(); + const mockKeyConnectorService = mock(); + const mockOrganizationApiService = mock(); + const mockDialogService = mock(); + const mockToastService = mock(); + + beforeEach(async () => { + jest.clearAllMocks(); + + await accountService.switchAccount(userId); + + component = new RemovePasswordComponent( + mock(), + mockRouter, + accountService, + mockSyncService, + mockI18nService, + mockKeyConnectorService, + mockOrganizationApiService, + mockDialogService, + mockToastService, + ); + }); + + describe("ngOnInit", () => { + it("should set activeUserId and organization", async () => { + mockKeyConnectorService.getManagingOrganization.mockResolvedValue(organization); + + await component.ngOnInit(); + + expect(component["activeUserId"]).toBe("test-user-id"); + expect(component.organization).toEqual(organization); + expect(component.loading).toEqual(false); + + expect(mockKeyConnectorService.getManagingOrganization).toHaveBeenCalledWith(userId); + expect(mockSyncService.fullSync).toHaveBeenCalledWith(false); + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + + it("should redirect to login when no active account is found", async () => { + await accountService.switchAccount(null as unknown as UserId); + + await component.ngOnInit(); + + expect(mockRouter.navigate).toHaveBeenCalledWith(["/"]); + }); + + it("should redirect to login when no organization is found", async () => { + mockKeyConnectorService.getManagingOrganization.mockResolvedValue( + null as unknown as Organization, + ); + + await component.ngOnInit(); + + expect(mockKeyConnectorService.getManagingOrganization).toHaveBeenCalledWith(userId); + expect(mockSyncService.fullSync).toHaveBeenCalledWith(false); + expect(mockRouter.navigate).toHaveBeenCalledWith(["/"]); + }); + }); + + describe("get action", () => { + it.each([ + [true, false], + [false, true], + [true, true], + ])( + "should return true when continuing is $continuing and leaving is $leaving", + (continuing, leaving) => { + component.continuing = continuing; + component.leaving = leaving; + + expect(component.action).toBe(true); + }, + ); + + it("should return false when continuing and leaving are both false", () => { + component.continuing = false; + component.leaving = false; + + expect(component.action).toBe(false); + }); + }); + + describe("convert", () => { + beforeEach(async () => { + mockKeyConnectorService.getManagingOrganization.mockResolvedValue(organization); + + await component.ngOnInit(); + }); + + it("should call migrateUser and show success toast", async () => { + mockI18nService.t.mockReturnValue("removed master password"); + + await component.convert(); + + expect(component.continuing).toBe(true); + expect(mockKeyConnectorService.migrateUser).toHaveBeenCalledWith(userId); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "success", + message: "removed master password", + }); + expect(mockRouter.navigate).toHaveBeenCalledWith(["/"]); + }); + + it("should handle errors and show error toast", async () => { + const errorMessage = "Can't migrate user error"; + mockKeyConnectorService.migrateUser.mockRejectedValue(new Error(errorMessage)); + mockI18nService.t.mockReturnValue("error occurred"); + + await component.convert(); + + expect(component.continuing).toBe(false); + expect(mockKeyConnectorService.migrateUser).toHaveBeenCalledWith(userId); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "error", + title: "error occurred", + message: errorMessage, + }); + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + + it("should handle error response and show error toast", async () => { + const errorMessage = "Can't migrate user error"; + mockKeyConnectorService.migrateUser.mockRejectedValue( + new ErrorResponse( + { + message: errorMessage, + }, + 404, + ), + ); + mockI18nService.t.mockReturnValue("error occurred"); + + await component.convert(); + + expect(component.continuing).toBe(false); + expect(mockKeyConnectorService.migrateUser).toHaveBeenCalledWith(userId); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "error", + title: "error occurred", + message: errorMessage, + }); + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + }); + + describe("leave", () => { + beforeEach(async () => { + mockKeyConnectorService.getManagingOrganization.mockResolvedValue(organization); + + await component.ngOnInit(); + }); + + it("should call leave and show success toast", async () => { + mockDialogService.openSimpleDialog.mockResolvedValue(true); + mockI18nService.t.mockReturnValue("left organization"); + + await component.leave(); + + expect(component.leaving).toBe(true); + expect(mockOrganizationApiService.leave).toHaveBeenCalledWith(organization.id); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "success", + message: "left organization", + }); + expect(mockRouter.navigate).toHaveBeenCalledWith(["/"]); + }); + + it("should handle error response and show error toast", async () => { + const errorMessage = "Can't leave organization error"; + mockDialogService.openSimpleDialog.mockResolvedValue(true); + mockOrganizationApiService.leave.mockRejectedValue(new Error(errorMessage)); + mockI18nService.t.mockReturnValue("error occurred"); + + await component.leave(); + + expect(component.leaving).toBe(false); + expect(mockOrganizationApiService.leave).toHaveBeenCalledWith(organization.id); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "error", + title: "error occurred", + message: errorMessage, + }); + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + + it("should handle error response and show error toast", async () => { + const errorMessage = "Can't leave organization error"; + mockDialogService.openSimpleDialog.mockResolvedValue(true); + mockOrganizationApiService.leave.mockRejectedValue( + new ErrorResponse( + { + message: errorMessage, + }, + 404, + ), + ); + mockI18nService.t.mockReturnValue("error occurred"); + + await component.leave(); + + expect(component.leaving).toBe(false); + expect(mockOrganizationApiService.leave).toHaveBeenCalledWith(organization.id); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "error", + title: "error occurred", + message: errorMessage, + }); + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + + it("should not call leave when dialog is canceled", async () => { + mockDialogService.openSimpleDialog.mockResolvedValue(false); + + await component.leave(); + + expect(component.leaving).toBe(false); + expect(mockOrganizationApiService.leave).not.toHaveBeenCalled(); + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/libs/key-management-ui/src/key-connector/remove-password.component.ts b/libs/key-management-ui/src/key-connector/remove-password.component.ts index d68644f588e..967e34223d1 100644 --- a/libs/key-management-ui/src/key-connector/remove-password.component.ts +++ b/libs/key-management-ui/src/key-connector/remove-password.component.ts @@ -1,33 +1,32 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Directive, OnInit } from "@angular/core"; import { Router } from "@angular/router"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; @Directive() export class RemovePasswordComponent implements OnInit { - actionPromise: Promise; continuing = false; leaving = false; loading = true; - organization: Organization; - email: string; + organization!: Organization; + private activeUserId!: UserId; constructor( + private logService: LogService, private router: Router, private accountService: AccountService, private syncService: SyncService, - private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private keyConnectorService: KeyConnectorService, private organizationApiService: OrganizationApiServiceAbstraction, @@ -36,35 +35,49 @@ export class RemovePasswordComponent implements OnInit { ) {} async ngOnInit() { - this.organization = await this.keyConnectorService.getManagingOrganization(); - this.email = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.email)), - ); + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + if (activeAccount == null) { + this.logService.info( + "[Key Connector remove password] No active account found, redirecting to login.", + ); + await this.router.navigate(["/"]); + return; + } + this.activeUserId = activeAccount.id; + await this.syncService.fullSync(false); + + this.organization = await this.keyConnectorService.getManagingOrganization(this.activeUserId); + if (this.organization == null) { + this.logService.info( + "[Key Connector remove password] No organization found, redirecting to login.", + ); + await this.router.navigate(["/"]); + return; + } this.loading = false; } + get action() { + return this.continuing || this.leaving; + } + convert = async () => { this.continuing = true; - this.actionPromise = this.keyConnectorService.migrateUser(); try { - await this.actionPromise; + await this.keyConnectorService.migrateUser(this.activeUserId); + this.toastService.showToast({ variant: "success", - title: null, message: this.i18nService.t("removedMasterPassword"), }); - await this.keyConnectorService.removeConvertAccountRequired(); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([""]); + + await this.router.navigate(["/"]); } catch (e) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: e.message, - }); + this.continuing = false; + + this.handleActionError(e); } }; @@ -79,25 +92,33 @@ export class RemovePasswordComponent implements OnInit { return false; } + this.leaving = true; try { - this.leaving = true; - this.actionPromise = this.organizationApiService.leave(this.organization.id); - await this.actionPromise; + await this.organizationApiService.leave(this.organization.id); + this.toastService.showToast({ variant: "success", - title: null, message: this.i18nService.t("leftOrganization"), }); - await this.keyConnectorService.removeConvertAccountRequired(); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([""]); + + await this.router.navigate(["/"]); } catch (e) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: e, - }); + this.leaving = false; + + this.handleActionError(e); } }; + + handleActionError(e: unknown) { + let message = ""; + if (e instanceof ErrorResponse || e instanceof Error) { + message = e.message; + } + + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: message, + }); + } } From a82eb8c22bcc4045b7337b761fd3eecf8613c3e6 Mon Sep 17 00:00:00 2001 From: Github Actions Date: Mon, 19 May 2025 10:40:05 +0000 Subject: [PATCH 106/245] Bumped client version(s) --- apps/browser/package.json | 2 +- apps/browser/src/manifest.json | 2 +- apps/browser/src/manifest.v3.json | 2 +- apps/cli/package.json | 2 +- apps/web/package.json | 2 +- package-lock.json | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/browser/package.json b/apps/browser/package.json index 6237d91d4db..c44743add7c 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2025.5.0", + "version": "2025.5.1", "scripts": { "build": "npm run build:chrome", "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 97b94e06fec..1c89916c6f7 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "Bitwarden", - "version": "2025.5.0", + "version": "2025.5.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 0f44b4af26d..834ac8d8b7d 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "Bitwarden", - "version": "2025.5.0", + "version": "2025.5.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/cli/package.json b/apps/cli/package.json index b01c96b23d1..18147cf4d2d 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/cli", "description": "A secure and free password manager for all of your devices.", - "version": "2025.4.0", + "version": "2025.5.0", "keywords": [ "bitwarden", "password", diff --git a/apps/web/package.json b/apps/web/package.json index 3884ed37e0d..6df2974129e 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2025.5.0", + "version": "2025.5.1", "scripts": { "build:oss": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", "build:bit": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/package-lock.json b/package-lock.json index 96cd9c0444a..c9b33a0df45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -190,11 +190,11 @@ }, "apps/browser": { "name": "@bitwarden/browser", - "version": "2025.5.0" + "version": "2025.5.1" }, "apps/cli": { "name": "@bitwarden/cli", - "version": "2025.4.0", + "version": "2025.5.0", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@koa/multer": "3.1.0", @@ -245,7 +245,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2025.5.0" + "version": "2025.5.1" }, "libs/admin-console": { "name": "@bitwarden/admin-console", From ef592bf23a5a68cb39d42c23c7cf562f516d2a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Mon, 19 May 2025 13:17:12 +0100 Subject: [PATCH 107/245] [PM-20050] Readonly members page for users with Account Recovery permission only (#14647) * Add SeparateCustomRolePermissions feature flag * Allow 'Manage Account Recovery' to be configured separately from 'Manage Users' * Add showUserManagementControls$ observable to show/hide user management controls based on user permissions * Update user management controls visibility to be dependant on user permissions. --- .../members/members.component.html | 387 +++++++++++------- .../members/members.component.ts | 15 + 2 files changed, 251 insertions(+), 151 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.html b/apps/web/src/app/admin-console/organizations/members/members.component.html index 2162e33081f..610821cfd1b 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.html +++ b/apps/web/src/app/admin-console/organizations/members/members.component.html @@ -5,7 +5,14 @@ [placeholder]="'searchMembers' | i18n" > - @@ -16,6 +23,7 @@ [selected]="status" (selectedChange)="statusToggle.next($event)" [attr.aria-label]="'memberStatusFilter' | i18n" + *ngIf="showUserManagementControls$ | async" > {{ "all" | i18n }} @@ -71,7 +79,7 @@ - + @@ -174,74 +183,143 @@ alignContent="middle" [ngClass]="rowHeightClass" > - + - -
- -
-
- - - {{ "invited" | i18n }} - - - {{ "needsConfirmation" | i18n }} - - - {{ "revoked" | i18n }} - -
-
- {{ u.email }} + + +
+ +
+
+ + + {{ "invited" | i18n }} + + + {{ "needsConfirmation" | i18n }} + + + {{ "revoked" | i18n }} + +
+
+ {{ u.email }} +
-
- + + + + +
+ +
+
+ {{ u.name ?? u.email }} + + {{ "invited" | i18n }} + + + {{ "needsConfirmation" | i18n }} + + + {{ "revoked" | i18n }} + +
+
+ {{ u.email }} +
+
+
+ +
- - - + + + + + + + + + + - - {{ u.type | userType }} - + + + {{ u.type | userType }} + + + + + {{ u.type | userType }} + + @@ -271,54 +349,58 @@ > - - - - - - - - + + + + + + + + + + + + - - - - + + + + + + + diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index 834aa2c7111..e5a94bc4b4f 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -46,7 +46,9 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { isNotSelfUpgradable, ProductTierType } from "@bitwarden/common/billing/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; @@ -102,6 +104,7 @@ export class MembersComponent extends BaseMembersComponent orgIsOnSecretsManagerStandalone = false; protected canUseSecretsManager$: Observable; + protected showUserManagementControls$: Observable; // Fixed sizes used for cdkVirtualScroll protected rowHeight = 69; @@ -135,6 +138,7 @@ export class MembersComponent extends BaseMembersComponent private collectionService: CollectionService, private billingApiService: BillingApiServiceAbstraction, protected deleteManagedMemberWarningService: DeleteManagedMemberWarningService, + private configService: ConfigService, ) { super( apiService, @@ -229,6 +233,17 @@ export class MembersComponent extends BaseMembersComponent takeUntilDestroyed(), ) .subscribe(); + + // Setup feature flag-dependent observables + const separateCustomRolePermissionsEnabled$ = this.configService.getFeatureFlag$( + FeatureFlag.SeparateCustomRolePermissions, + ); + this.showUserManagementControls$ = separateCustomRolePermissionsEnabled$.pipe( + map( + (separateCustomRolePermissionsEnabled) => + !separateCustomRolePermissionsEnabled || this.organization.canManageUsers, + ), + ); } async getUsers(): Promise { From 239556b55f666ff887c7b2bc1d418e852b5434f7 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Mon, 19 May 2025 14:58:51 +0200 Subject: [PATCH 108/245] [PM-18017] Show key connector domain in remove password page (#14695) * Passed in userId on RemovePasswordComponent. * Added userId on other references to KeyConnectorService methods * remove password component refactor, test coverage, enabled strict * explicit user id provided to key connector service * redirect to / instead when user not logged in or not managing organization * key connector service explicit user id * key connector service no longer requires account service * key connector service missing null type * cli convert to key connector unit tests * remove unnecessary SyncService * error toast not showing on ErrorResponse * bad import due to merge conflict * bad import due to merge conflict * missing loading in remove password component for browser extension * error handling in remove password component * organization observable race condition in key-connector * usesKeyConnector always returns boolean * unit test coverage * key connector reactive * reactive key connector service * introducing convertAccountRequired$ * cli build fix * moving message sending side effect to sync * key connector service unit tests * fix unit tests * move key connector components to KM team ownership * new unit tests in wrong place * key connector domain shown in remove password component * type safety improvements * convert to key connector command localization * key connector domain in convert to key connector command * convert to key connector command unit tests with prompt assert * organization name placement change in the remove password component * unit test update * key connector url required to be provided when migrating user * unit tests in wrong place after KM code ownership move * infinite page reload * failing unit tests * failing unit tests --------- Co-authored-by: Todd Martin --- apps/browser/src/_locales/en/messages.json | 16 +-- .../remove-password.component.html | 6 +- apps/cli/src/auth/commands/unlock.command.ts | 3 + apps/cli/src/base-program.ts | 1 + .../convert-to-key-connector.command.spec.ts | 76 ++++++++++++- .../convert-to-key-connector.command.ts | 23 ++-- apps/cli/src/locales/en/messages.json | 28 +++++ apps/cli/src/oss-serve-configurator.ts | 1 + apps/cli/src/program.ts | 1 + .../remove-password.component.html | 6 +- apps/desktop/src/locales/en/messages.json | 16 +-- .../remove-password.component.html | 6 +- apps/web/src/locales/en/messages.json | 13 +-- .../abstractions/key-connector.service.ts | 4 +- .../services/key-connector.service.spec.ts | 106 ++++-------------- .../services/key-connector.service.ts | 14 +-- .../remove-password.component.spec.ts | 16 ++- .../remove-password.component.ts | 5 +- 18 files changed, 201 insertions(+), 140 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index f436c45ab75..3a5583f5468 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/browser/src/key-management/key-connector/remove-password.component.html b/apps/browser/src/key-management/key-connector/remove-password.component.html index 272f727a7bb..56baf0de2a0 100644 --- a/apps/browser/src/key-management/key-connector/remove-password.component.html +++ b/apps/browser/src/key-management/key-connector/remove-password.component.html @@ -15,7 +15,11 @@
-

{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}

+

{{ "removeMasterPasswordForOrganizationUserKeyConnector" | i18n }}

+

{{ "organizationName" | i18n }}:

+

{{ organization.name }}

+

{{ "keyConnectorDomain" | i18n }}:

+

{{ organization.keyConnectorUrl }}

-

{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}

+

{{ "removeMasterPasswordForOrganizationUserKeyConnector" | i18n }}

+

{{ "organizationName" | i18n }}:

+

{{ organization.name }}

+

{{ "keyConnectorDomain" | i18n }}:

+

{{ organization.keyConnectorUrl }}

- + diff --git a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts index 7ac0f8a6726..e0ec1358ee1 100644 --- a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts +++ b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts @@ -6,13 +6,14 @@ import { Component, Input } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view"; import { - CardComponent, SectionHeaderComponent, TypographyModule, FormFieldModule, IconButtonModule, } from "@bitwarden/components"; +import { ReadOnlyCipherCardComponent } from "../read-only-cipher-card/read-only-cipher-card.component"; + @Component({ selector: "app-sshkey-view", templateUrl: "sshkey-view.component.html", @@ -20,8 +21,8 @@ import { imports: [ CommonModule, JslibModule, - CardComponent, SectionHeaderComponent, + ReadOnlyCipherCardComponent, TypographyModule, FormFieldModule, IconButtonModule, From 7641dab0f06df63aeb655c4bd805b127ff0b31e6 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 20 May 2025 12:19:34 -0700 Subject: [PATCH 121/245] [PM-18801] - account security nudge (#14771) * account security nudge * fix messages.json * fix tests * fix logic for account security item * fix tests * adjust account security nudge work to updated nudge service * fix account security nudge * remove unused code. do not show account security badge * include ff and safari in link html * fix import * Revert "include ff and safari in link html" This reverts commit cd12a36274a082c62caae010812d742ec11e4696. --- apps/browser/src/_locales/en/messages.json | 9 ++++ .../settings/account-security.component.html | 7 +++ .../account-security.component.spec.ts | 14 ++++++ .../settings/account-security.component.ts | 26 ++++++++++ .../popup/settings/settings-v2.component.html | 2 +- .../popup/settings/settings-v2.component.ts | 2 +- .../account-security-nudge.service.ts | 49 +++++++++++++++++++ .../services/custom-nudges-services/index.ts | 1 + .../src/vault/services/nudges.service.spec.ts | 10 ++++ .../src/vault/services/nudges.service.ts | 3 ++ 10 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 3a5583f5468..99ca31bafd5 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5019,6 +5019,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/auth/popup/settings/account-security.component.html b/apps/browser/src/auth/popup/settings/account-security.component.html index ebf79af644c..d835497d9be 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.html +++ b/apps/browser/src/auth/popup/settings/account-security.component.html @@ -5,6 +5,13 @@ +
diff --git a/apps/browser/src/auth/popup/settings/account-security.component.spec.ts b/apps/browser/src/auth/popup/settings/account-security.component.spec.ts index abe642970bb..56b18068778 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.spec.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.spec.ts @@ -4,7 +4,10 @@ import { By } from "@angular/platform-browser"; import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; +import { CollectionService } from "@bitwarden/admin-console/common"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; @@ -16,14 +19,18 @@ import { VaultTimeoutStringType, VaultTimeoutAction, } from "@bitwarden/common/key-management/vault-timeout"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { MessageSender } from "@bitwarden/common/platform/messaging"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { StateProvider } from "@bitwarden/common/platform/state"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { BiometricStateService, BiometricsService, KeyService } from "@bitwarden/key-management"; @@ -71,6 +78,13 @@ describe("AccountSecurityComponent", () => { { provide: UserVerificationService, useValue: mock() }, { provide: VaultTimeoutService, useValue: mock() }, { provide: VaultTimeoutSettingsService, useValue: vaultTimeoutSettingsService }, + { provide: StateProvider, useValue: mock() }, + { provide: CipherService, useValue: mock() }, + { provide: ApiService, useValue: mock() }, + { provide: LogService, useValue: mock() }, + { provide: OrganizationService, useValue: mock() }, + { provide: CollectionService, useValue: mock() }, + { provide: ConfigService, useValue: mock() }, ], }) .overrideComponent(AccountSecurityComponent, { diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index ede044b21de..b2380a1a47e 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -22,6 +22,7 @@ import { } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; import { FingerprintDialogComponent, VaultTimeoutInputComponent } from "@bitwarden/auth/angular"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -64,6 +65,7 @@ import { BiometricStateService, BiometricsStatus, } from "@bitwarden/key-management"; +import { SpotlightComponent } from "@bitwarden/vault"; import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors"; import { BrowserApi } from "../../../platform/browser/browser-api"; @@ -96,6 +98,7 @@ import { AwaitDesktopDialogComponent } from "./await-desktop-dialog.component"; SectionComponent, SectionHeaderComponent, SelectModule, + SpotlightComponent, TypographyModule, VaultTimeoutInputComponent, ], @@ -120,6 +123,14 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { enableAutoBiometricsPrompt: true, }); + protected showAccountSecurityNudge$: Observable = + this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => + this.vaultNudgesService.showNudgeSpotlight$(NudgeType.AccountSecurity, userId), + ), + ); + private refreshTimeoutSettings$ = new BehaviorSubject(undefined); private destroy$ = new Subject(); @@ -142,6 +153,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { private biometricStateService: BiometricStateService, private toastService: ToastService, private biometricsService: BiometricsService, + private vaultNudgesService: NudgesService, ) {} async ngOnInit() { @@ -402,6 +414,14 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { } } + protected async dismissAccountSecurityNudge() { + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + if (!activeAccount) { + return; + } + await this.vaultNudgesService.dismissNudge(NudgeType.AccountSecurity, activeAccount.id); + } + async saveVaultTimeoutAction(value: VaultTimeoutAction) { if (value === VaultTimeoutAction.LogOut) { const confirmed = await this.dialogService.openSimpleDialog({ @@ -453,6 +473,12 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { this.form.controls.pin.setValue(userHasPinSet, { emitEvent: false }); const requireReprompt = (await this.pinService.getPinLockType(userId)) == "EPHEMERAL"; this.form.controls.pinLockWithMasterPassword.setValue(requireReprompt, { emitEvent: false }); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("unlockPinSet"), + }); + await this.vaultNudgesService.dismissNudge(NudgeType.AccountSecurity, userId); } else { await this.vaultTimeoutSettingsService.clear(); } diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.html b/apps/browser/src/tools/popup/settings/settings-v2.component.html index dc53f95a7cf..0b2e84712a4 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.html +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.html @@ -82,7 +82,7 @@

{{ "downloadBitwardenOnAllDevices" | i18n }}

= this.authenticatedAccount$.pipe( + showDownloadBitwardenNudge$: Observable = this.authenticatedAccount$.pipe( switchMap((account) => this.nudgesService.showNudgeBadge$(NudgeType.DownloadBitwarden, account.id), ), diff --git a/libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts b/libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts new file mode 100644 index 00000000000..862beb333d4 --- /dev/null +++ b/libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts @@ -0,0 +1,49 @@ +import { Injectable, inject } from "@angular/core"; +import { Observable, combineLatest, from, of } from "rxjs"; +import { catchError, map } from "rxjs/operators"; + +import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service"; +import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserId } from "@bitwarden/common/types/guid"; + +import { DefaultSingleNudgeService } from "../default-single-nudge.service"; +import { NudgeStatus, NudgeType } from "../nudges.service"; + +const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000; + +@Injectable({ providedIn: "root" }) +export class AccountSecurityNudgeService extends DefaultSingleNudgeService { + private vaultProfileService = inject(VaultProfileService); + private logService = inject(LogService); + private pinService = inject(PinServiceAbstraction); + private vaultTimeoutSettingsService = inject(VaultTimeoutSettingsService); + + nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable { + const profileDate$ = from(this.vaultProfileService.getProfileCreationDate(userId)).pipe( + catchError(() => { + this.logService.error("Failed to load profile date:"); + // Default to today to ensure the nudge is shown in case of an error + return of(new Date()); + }), + ); + + return combineLatest([ + profileDate$, + this.getNudgeStatus$(nudgeType, userId), + of(Date.now() - THIRTY_DAYS_MS), + from(this.pinService.isPinSet(userId)), + from(this.vaultTimeoutSettingsService.isBiometricLockSet(userId)), + ]).pipe( + map(([profileCreationDate, status, profileCutoff, isPinSet, isBiometricLockSet]) => { + const profileOlderThanCutoff = profileCreationDate.getTime() < profileCutoff; + const hideNudge = profileOlderThanCutoff || isPinSet || isBiometricLockSet; + return { + hasBadgeDismissed: status.hasBadgeDismissed || hideNudge, + hasSpotlightDismissed: status.hasSpotlightDismissed || hideNudge, + }; + }), + ); + } +} diff --git a/libs/angular/src/vault/services/custom-nudges-services/index.ts b/libs/angular/src/vault/services/custom-nudges-services/index.ts index 2e9ade985cc..e94b8bf71e5 100644 --- a/libs/angular/src/vault/services/custom-nudges-services/index.ts +++ b/libs/angular/src/vault/services/custom-nudges-services/index.ts @@ -1,4 +1,5 @@ export * from "./autofill-nudge.service"; +export * from "./account-security-nudge.service"; export * from "./has-items-nudge.service"; export * from "./download-bitwarden-nudge.service"; export * from "./empty-vault-nudge.service"; diff --git a/libs/angular/src/vault/services/nudges.service.spec.ts b/libs/angular/src/vault/services/nudges.service.spec.ts index 4eee349cf10..4edd57f5428 100644 --- a/libs/angular/src/vault/services/nudges.service.spec.ts +++ b/libs/angular/src/vault/services/nudges.service.spec.ts @@ -2,8 +2,10 @@ import { TestBed } from "@angular/core/testing"; import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; +import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { StateProvider } from "@bitwarden/common/platform/state"; @@ -74,6 +76,14 @@ describe("Vault Nudges Service", () => { provide: LogService, useValue: mock(), }, + { + provide: PinServiceAbstraction, + useValue: mock(), + }, + { + provide: VaultTimeoutSettingsService, + useValue: mock(), + }, ], }); }); diff --git a/libs/angular/src/vault/services/nudges.service.ts b/libs/angular/src/vault/services/nudges.service.ts index 6784e9c7b4e..55e6009a8e0 100644 --- a/libs/angular/src/vault/services/nudges.service.ts +++ b/libs/angular/src/vault/services/nudges.service.ts @@ -12,6 +12,7 @@ import { AutofillNudgeService, DownloadBitwardenNudgeService, NewItemNudgeService, + AccountSecurityNudgeService, } from "./custom-nudges-services"; import { DefaultSingleNudgeService, SingleNudgeService } from "./default-single-nudge.service"; @@ -32,6 +33,7 @@ export enum NudgeType { EmptyVaultNudge = "empty-vault-nudge", HasVaultItems = "has-vault-items", AutofillNudge = "autofill-nudge", + AccountSecurity = "account-security", DownloadBitwarden = "download-bitwarden", NewLoginItemStatus = "new-login-item-status", NewCardItemStatus = "new-card-item-status", @@ -61,6 +63,7 @@ export class NudgesService { private customNudgeServices: Partial> = { [NudgeType.HasVaultItems]: inject(HasItemsNudgeService), [NudgeType.EmptyVaultNudge]: inject(EmptyVaultNudgeService), + [NudgeType.AccountSecurity]: inject(AccountSecurityNudgeService), [NudgeType.AutofillNudge]: inject(AutofillNudgeService), [NudgeType.DownloadBitwarden]: inject(DownloadBitwardenNudgeService), [NudgeType.NewLoginItemStatus]: this.newItemNudgeService, From d7c936e1ea07338aafe15b6e055c28f5589cf383 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 20 May 2025 21:25:14 +0200 Subject: [PATCH 122/245] [PM-17900] Add cose / xchacha20poly1305 migration on userkey rotation (#14539) * Add new encrypt service functions * Undo changes * Cleanup * Fix build * Fix comments * Switch encrypt service to use SDK functions * Add cose migration on userkey rotation * Update sdk * Set featureflag to default disabled * Add tests * Update sdk to build 168 * Make changes according to feedback --- .../user-key-rotation.service.spec.ts | 78 +++++++++++++++---- .../key-rotation/user-key-rotation.service.ts | 25 +++++- libs/common/src/enums/feature-flag.enum.ts | 2 + .../platform/enums/encryption-type.enum.ts | 8 ++ .../models/domain/symmetric-crypto-key.ts | 18 ++++- libs/key-management/src/models/kdf-config.ts | 19 +++++ package-lock.json | 8 +- package.json | 2 +- 8 files changed, 133 insertions(+), 27 deletions(-) diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts index dac5afa191a..c65c4ac3ea4 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts @@ -11,7 +11,6 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; @@ -30,6 +29,7 @@ import { EmergencyAccessTrustComponent, KeyRotationTrustInfoComponent, } from "@bitwarden/key-management-ui"; +import { PureCrypto } from "@bitwarden/sdk-internal"; import { OrganizationUserResetPasswordService } from "../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service"; import { WebauthnLoginAdminService } from "../../auth"; @@ -96,6 +96,11 @@ describe("KeyRotationService", () => { const mockTrustedPublicKeys = [Utils.fromUtf8ToArray("test-public-key")]; beforeAll(() => { + jest.spyOn(PureCrypto, "make_user_key_aes256_cbc_hmac").mockReturnValue(new Uint8Array(64)); + jest.spyOn(PureCrypto, "make_user_key_xchacha20_poly1305").mockReturnValue(new Uint8Array(70)); + jest + .spyOn(PureCrypto, "encrypt_user_key_with_master_password") + .mockReturnValue("mockNewUserKey"); mockUserVerificationService = mock(); mockApiService = mock(); mockCipherService = mock(); @@ -158,6 +163,7 @@ describe("KeyRotationService", () => { mockToastService, mockI18nService, mockDialogService, + mockConfigService, ); }); @@ -181,7 +187,7 @@ describe("KeyRotationService", () => { } as any, ]); mockKeyService.hashMasterKey.mockResolvedValue("mockMasterPasswordHash"); - mockConfigService.getFeatureFlag.mockResolvedValue(true); + mockConfigService.getFeatureFlag.mockResolvedValue(false); mockEncryptService.wrapSymmetricKey.mockResolvedValue({ encryptedString: "mockEncryptedData", @@ -286,6 +292,59 @@ describe("KeyRotationService", () => { expect(arg.accountUnlockData.emergencyAccessUnlockData.length).toBe(1); expect(arg.accountUnlockData.organizationAccountRecoveryUnlockData.length).toBe(1); expect(arg.accountUnlockData.passkeyUnlockData.length).toBe(2); + expect(PureCrypto.make_user_key_aes256_cbc_hmac).toHaveBeenCalled(); + expect(PureCrypto.encrypt_user_key_with_master_password).toHaveBeenCalledWith( + new Uint8Array(64), + "newMasterPassword", + mockUser.email, + DEFAULT_KDF_CONFIG.toSdkConfig(), + ); + expect(PureCrypto.make_user_key_xchacha20_poly1305).not.toHaveBeenCalled(); + }); + + it("rotates the userkey to xchacha20poly1305 and encrypted data and changes master password when featureflag is active", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); + + KeyRotationTrustInfoComponent.open = initialPromptedOpenTrue; + EmergencyAccessTrustComponent.open = emergencyAccessTrustOpenTrusted; + AccountRecoveryTrustComponent.open = accountRecoveryTrustOpenTrusted; + await keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData( + "mockMasterPassword", + "newMasterPassword", + mockUser, + ); + + expect(mockApiService.postUserKeyUpdateV2).toHaveBeenCalled(); + const arg = mockApiService.postUserKeyUpdateV2.mock.calls[0][0]; + expect(arg.accountUnlockData.masterPasswordUnlockData.masterKeyEncryptedUserKey).toBe( + "mockNewUserKey", + ); + expect(arg.oldMasterKeyAuthenticationHash).toBe("mockMasterPasswordHash"); + expect(arg.accountUnlockData.masterPasswordUnlockData.email).toBe("mockEmail"); + expect(arg.accountUnlockData.masterPasswordUnlockData.kdfType).toBe( + DEFAULT_KDF_CONFIG.kdfType, + ); + expect(arg.accountUnlockData.masterPasswordUnlockData.kdfIterations).toBe( + DEFAULT_KDF_CONFIG.iterations, + ); + + expect(arg.accountKeys.accountPublicKey).toBe(Utils.fromUtf8ToB64("mockPublicKey")); + expect(arg.accountKeys.userKeyEncryptedAccountPrivateKey).toBe("mockEncryptedData"); + + expect(arg.accountData.ciphers.length).toBe(2); + expect(arg.accountData.folders.length).toBe(2); + expect(arg.accountData.sends.length).toBe(2); + expect(arg.accountUnlockData.emergencyAccessUnlockData.length).toBe(1); + expect(arg.accountUnlockData.organizationAccountRecoveryUnlockData.length).toBe(1); + expect(arg.accountUnlockData.passkeyUnlockData.length).toBe(2); + expect(PureCrypto.make_user_key_aes256_cbc_hmac).not.toHaveBeenCalled(); + expect(PureCrypto.encrypt_user_key_with_master_password).toHaveBeenCalledWith( + new Uint8Array(70), + "newMasterPassword", + mockUser.email, + DEFAULT_KDF_CONFIG.toSdkConfig(), + ); + expect(PureCrypto.make_user_key_xchacha20_poly1305).toHaveBeenCalled(); }); it("returns early when first trust warning dialog is declined", async () => { @@ -344,21 +403,6 @@ describe("KeyRotationService", () => { ).rejects.toThrow(); }); - it("throws if user key creation fails", async () => { - mockKeyService.makeUserKey.mockResolvedValueOnce([ - null as unknown as UserKey, - null as unknown as EncString, - ]); - - await expect( - keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData( - "mockMasterPassword", - "mockMasterPassword1", - mockUser, - ), - ).rejects.toThrow(); - }); - it("legacy throws if no private key is found", async () => { privateKey.next(null); diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts index ec38f49baeb..129d643f677 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts @@ -5,14 +5,17 @@ import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { HashPurpose } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; @@ -26,6 +29,7 @@ import { EmergencyAccessTrustComponent, KeyRotationTrustInfoComponent, } from "@bitwarden/key-management-ui"; +import { PureCrypto } from "@bitwarden/sdk-internal"; import { OrganizationUserResetPasswordService } from "../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service"; import { WebauthnLoginAdminService } from "../../auth/core"; @@ -59,6 +63,7 @@ export class UserKeyRotationService { private toastService: ToastService, private i18nService: I18nService, private dialogService: DialogService, + private configService: ConfigService, ) {} /** @@ -116,8 +121,22 @@ export class UserKeyRotationService { const newMasterKey = await this.keyService.makeMasterKey(newMasterPassword, email, kdfConfig); - const [newUnencryptedUserKey, newMasterKeyEncryptedUserKey] = - await this.keyService.makeUserKey(newMasterKey); + let userKeyBytes: Uint8Array; + if (await this.configService.getFeatureFlag(FeatureFlag.EnrollAeadOnKeyRotation)) { + userKeyBytes = PureCrypto.make_user_key_xchacha20_poly1305(); + } else { + userKeyBytes = PureCrypto.make_user_key_aes256_cbc_hmac(); + } + + const newMasterKeyEncryptedUserKey = new EncString( + PureCrypto.encrypt_user_key_with_master_password( + userKeyBytes, + newMasterPassword, + email, + kdfConfig.toSdkConfig(), + ), + ); + const newUnencryptedUserKey = new SymmetricCryptoKey(userKeyBytes) as UserKey; if (!newUnencryptedUserKey || !newMasterKeyEncryptedUserKey) { this.logService.info("[Userkey rotation] User key could not be created. Aborting!"); diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index c71215a4a09..f9e68efe4be 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -48,6 +48,7 @@ export enum FeatureFlag { PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service", UseSDKForDecryption = "use-sdk-for-decryption", PM17987_BlockType0 = "pm-17987-block-type-0", + EnrollAeadOnKeyRotation = "enroll-aead-on-key-rotation", /* Tools */ ItemShare = "item-share", @@ -131,6 +132,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.PM4154_BulkEncryptionService]: FALSE, [FeatureFlag.UseSDKForDecryption]: FALSE, [FeatureFlag.PM17987_BlockType0]: FALSE, + [FeatureFlag.EnrollAeadOnKeyRotation]: FALSE, /* Platform */ [FeatureFlag.IpcChannelFramework]: FALSE, diff --git a/libs/common/src/platform/enums/encryption-type.enum.ts b/libs/common/src/platform/enums/encryption-type.enum.ts index 7f4b5048a45..426b1a23134 100644 --- a/libs/common/src/platform/enums/encryption-type.enum.ts +++ b/libs/common/src/platform/enums/encryption-type.enum.ts @@ -1,9 +1,16 @@ // FIXME: update to use a const object instead of a typescript enum // eslint-disable-next-line @bitwarden/platform/no-enums export enum EncryptionType { + // Symmetric encryption types AesCbc256_B64 = 0, // Type 1 was the unused and removed AesCbc128_HmacSha256_B64 AesCbc256_HmacSha256_B64 = 2, + // Cose is the encoding for the key used, but contained can be: + // - XChaCha20Poly1305 + CoseEncrypt0 = 7, + + // Asymmetric encryption types. These never occur in the same places that the symmetric ones would + // and can be split out into a separate enum. Rsa2048_OaepSha256_B64 = 3, Rsa2048_OaepSha1_B64 = 4, Rsa2048_OaepSha256_HmacSha256_B64 = 5, @@ -38,4 +45,5 @@ export const EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE = { [EncryptionType.Rsa2048_OaepSha1_B64]: 1, [EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64]: 2, [EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64]: 2, + [EncryptionType.CoseEncrypt0]: 1, }; diff --git a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts index ad16ddd06f6..1fdca04aceb 100644 --- a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts +++ b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts @@ -16,13 +16,19 @@ export type Aes256CbcKey = { encryptionKey: Uint8Array; }; +export type CoseKey = { + type: EncryptionType.CoseEncrypt0; + // Encryption key here refers to the cose-encoded and padded key. This MAY later be refactored to contain the actual key bytes, as is the case in the SDK + encryptionKey: Uint8Array; +}; + /** * A symmetric crypto key represents a symmetric key usable for symmetric encryption and decryption operations. * The specific algorithm used is private to the key, and should only be exposed to encrypt service implementations. * This can be done via `inner()`. */ export class SymmetricCryptoKey { - private innerKey: Aes256CbcHmacKey | Aes256CbcKey; + private innerKey: Aes256CbcHmacKey | Aes256CbcKey | CoseKey; keyB64: string; @@ -47,6 +53,12 @@ export class SymmetricCryptoKey { authenticationKey: key.slice(32), }; this.keyB64 = this.toBase64(); + } else if (key.byteLength > 64) { + this.innerKey = { + type: EncryptionType.CoseEncrypt0, + encryptionKey: key, + }; + this.keyB64 = this.toBase64(); } else { throw new Error(`Unsupported encType/key length ${key.byteLength}`); } @@ -63,7 +75,7 @@ export class SymmetricCryptoKey { * * @returns The inner key instance that can be directly used for encryption primitives */ - inner(): Aes256CbcHmacKey | Aes256CbcKey { + inner(): Aes256CbcHmacKey | Aes256CbcKey | CoseKey { return this.innerKey; } @@ -90,6 +102,8 @@ export class SymmetricCryptoKey { encodedKey.set(this.innerKey.encryptionKey, 0); encodedKey.set(this.innerKey.authenticationKey, 32); return encodedKey; + } else if (this.innerKey.type === EncryptionType.CoseEncrypt0) { + return this.innerKey.encryptionKey; } else { throw new Error("Unsupported encryption type."); } diff --git a/libs/key-management/src/models/kdf-config.ts b/libs/key-management/src/models/kdf-config.ts index 689da77c163..a2ed8a22505 100644 --- a/libs/key-management/src/models/kdf-config.ts +++ b/libs/key-management/src/models/kdf-config.ts @@ -1,6 +1,7 @@ import { Jsonify } from "type-fest"; import { RangeWithDefault } from "@bitwarden/common/platform/misc/range-with-default"; +import { Kdf } from "@bitwarden/sdk-internal"; import { KdfType } from "../enums/kdf-type.enum"; @@ -49,6 +50,14 @@ export class PBKDF2KdfConfig { static fromJSON(json: Jsonify): PBKDF2KdfConfig { return new PBKDF2KdfConfig(json.iterations); } + + toSdkConfig(): Kdf { + return { + pBKDF2: { + iterations: this.iterations, + }, + }; + } } /** @@ -124,6 +133,16 @@ export class Argon2KdfConfig { static fromJSON(json: Jsonify): Argon2KdfConfig { return new Argon2KdfConfig(json.iterations, json.memory, json.parallelism); } + + toSdkConfig(): Kdf { + return { + argon2id: { + iterations: this.iterations, + memory: this.memory, + parallelism: this.parallelism, + }, + }; + } } export const DEFAULT_KDF_CONFIG = new PBKDF2KdfConfig(PBKDF2KdfConfig.ITERATIONS.defaultValue); diff --git a/package-lock.json b/package-lock.json index 9707005d892..bd2cabe1cfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@angular/platform-browser": "18.2.13", "@angular/platform-browser-dynamic": "18.2.13", "@angular/router": "18.2.13", - "@bitwarden/sdk-internal": "0.2.0-main.159", + "@bitwarden/sdk-internal": "0.2.0-main.168", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "3.1.0", @@ -4830,9 +4830,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.2.0-main.159", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.159.tgz", - "integrity": "sha512-vliX5w/A6fuKWZJpDZTCPV4EU5CFrrs6zAv0aQaUQXF9LqL1YVh113D1NhOMuG2ILLWs2kDcTKiprvWFSTu1dg==", + "version": "0.2.0-main.168", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.168.tgz", + "integrity": "sha512-NU10oqw+GI9oHrh8/i/IC8/7oaYmswqC2E/0Zc56xC3jY7uNgFZgpae7JhyMU6UxzrAjiEqdmGnm+AGWFiPG8w==", "license": "GPL-3.0" }, "node_modules/@bitwarden/send-ui": { diff --git a/package.json b/package.json index fc948fc5ee6..9831417de59 100644 --- a/package.json +++ b/package.json @@ -159,7 +159,7 @@ "@angular/platform-browser": "18.2.13", "@angular/platform-browser-dynamic": "18.2.13", "@angular/router": "18.2.13", - "@bitwarden/sdk-internal": "0.2.0-main.159", + "@bitwarden/sdk-internal": "0.2.0-main.168", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "3.1.0", From 94815085ed0e463c325a7f7018f7c626b60b0361 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 20 May 2025 13:36:10 -0700 Subject: [PATCH 123/245] set default values for navButtons$ observable (#14859) --- apps/browser/src/popup/tabs-v2.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/popup/tabs-v2.component.ts b/apps/browser/src/popup/tabs-v2.component.ts index 54aa4a5544d..3d93f5d4e04 100644 --- a/apps/browser/src/popup/tabs-v2.component.ts +++ b/apps/browser/src/popup/tabs-v2.component.ts @@ -1,5 +1,5 @@ import { Component } from "@angular/core"; -import { combineLatest, map, Observable, switchMap } from "rxjs"; +import { combineLatest, map, Observable, startWith, switchMap } from "rxjs"; import { NudgesService } from "@bitwarden/angular/vault"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -23,6 +23,7 @@ export class TabsV2Component { this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge), this.hasActiveBadges$, ]).pipe( + startWith([false, false]), map(([onboardingFeatureEnabled, hasBadges]) => { return [ { From bc56bc8e37b8d58683b146b8c95769d2d5ea351c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 May 2025 09:29:14 +0200 Subject: [PATCH 124/245] [deps]: Lock file maintenance (#13866) * [deps]: Lock file maintenance * add override for parse5 version to 7.2.1 (7.3.0 has breaking ts changes) * manual rebuild of package-lock * fix test event listeners persistence Co-authored-by: Oscar Hinton --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Jonathan Prusik Co-authored-by: Oscar Hinton --- .../list/autofill-inline-menu-list.spec.ts | 11 +- apps/desktop/desktop_native/Cargo.lock | 481 ++--- .../package-lock.json | 6 +- package-lock.json | 1859 ++++++++--------- package.json | 1 + 5 files changed, 1160 insertions(+), 1198 deletions(-) diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.spec.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.spec.ts index ed28375e4fe..b4e480797da 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.spec.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.spec.ts @@ -21,10 +21,16 @@ describe("AutofillInlineMenuList", () => { disconnect: jest.fn(), })); - let autofillInlineMenuList: AutofillInlineMenuList; + let autofillInlineMenuList: AutofillInlineMenuList | null; const portKey: string = "inlineMenuListPortKey"; + const events: { eventName: any; callback: any }[] = []; beforeEach(() => { + const oldEv = globalThis.addEventListener; + globalThis.addEventListener = (eventName: any, callback: any) => { + events.push({ eventName, callback }); + oldEv.call(globalThis, eventName, callback); + }; document.body.innerHTML = ``; autofillInlineMenuList = document.querySelector("autofill-inline-menu-list"); jest.spyOn(globalThis.document, "createElement"); @@ -33,6 +39,9 @@ describe("AutofillInlineMenuList", () => { afterEach(() => { jest.clearAllMocks(); + events.forEach(({ eventName, callback }) => { + globalThis.removeEventListener(eventName, callback); + }); }); describe("initAutofillInlineMenuList", () => { diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index f3db03f8639..242c1e1b9f2 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -232,14 +232,15 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", + "pin-project-lite", "slab", ] @@ -267,7 +268,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix", + "rustix 0.38.44", "slab", "tracing", "windows-sys 0.59.0", @@ -299,7 +300,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix", + "rustix 0.38.44", "tracing", ] @@ -326,7 +327,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix", + "rustix 0.38.44", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -340,9 +341,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", @@ -363,9 +364,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -384,15 +385,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" [[package]] name = "basic-toml" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" dependencies = [ "serde", ] @@ -419,9 +420,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bitwarden-russh" @@ -497,9 +498,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "camino" @@ -587,9 +588,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.31" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" dependencies = [ "clap_builder", "clap_derive", @@ -597,9 +598,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" dependencies = [ "anstream", "anstyle", @@ -609,9 +610,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck", "proc-macro2", @@ -636,10 +637,11 @@ dependencies = [ [[package]] name = "codespan-reporting" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" dependencies = [ + "serde", "termcolor", "unicode-width", ] @@ -763,9 +765,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.141" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bc580dceb395cae0efdde0a88f034cfd8a276897e40c693a7b87bed17971d33" +checksum = "a71ea7f29c73f7ffa64c50b83c9fe4d3a6d4be89a86b009eb80d5a6d3429d741" dependencies = [ "cc", "cxxbridge-cmd", @@ -777,9 +779,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.141" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49d8c1baedad72a7efda12ad8d7ad687b3e7221dfb304a12443fd69e9de8bb30" +checksum = "36a8232661d66dcf713394726157d3cfe0a89bfc85f52d6e9f9bbc2306797fe7" dependencies = [ "cc", "codespan-reporting", @@ -791,9 +793,9 @@ dependencies = [ [[package]] name = "cxxbridge-cmd" -version = "1.0.141" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e43afb0e3b2ef293492a31ecd796af902112460d53e5f923f7804f348a769f9c" +checksum = "4f44296c8693e9ea226a48f6a122727f77aa9e9e338380cb021accaeeb7ee279" dependencies = [ "clap", "codespan-reporting", @@ -804,15 +806,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.141" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0257ad2096a2474fe877e9e055ab69603851c3d6b394efcc7e0443899c2492ce" +checksum = "c42f69c181c176981ae44ba9876e2ea41ce8e574c296b38d06925ce9214fb8e4" [[package]] name = "cxxbridge-macro" -version = "1.0.141" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b46cbd7358a46b760609f1cb5093683328e58ca50e594a308716f5403fdc03e5" +checksum = "8faff5d4467e0709448187df29ccbf3b0982cc426ee444a193f87b11afb565a8" dependencies = [ "proc-macro2", "quote", @@ -835,9 +837,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", @@ -846,9 +848,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] @@ -1078,9 +1080,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -1088,9 +1090,9 @@ dependencies = [ [[package]] name = "error-code" -version = "3.3.1" +version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "event-listener" @@ -1105,9 +1107,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener", "pin-project-lite", @@ -1139,9 +1141,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" @@ -1285,9 +1287,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -1296,14 +1298,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -1347,9 +1349,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" [[package]] name = "heck" @@ -1401,21 +1403,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -1424,31 +1427,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -1456,67 +1439,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "idna" version = "1.0.3" @@ -1530,9 +1500,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -1540,12 +1510,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.3", ] [[package]] @@ -1581,9 +1551,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "keytar" @@ -1623,19 +1593,19 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.0", ] [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" @@ -1649,9 +1619,9 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +checksum = "4a6f6da007f968f9def0d65a05b187e2960183de70c160204ecfccf0ee330212" dependencies = [ "cc", ] @@ -1663,10 +1633,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] -name = "litemap" -version = "0.7.5" +name = "linux-raw-sys" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" @@ -1748,9 +1724,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] @@ -2057,9 +2033,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oo7" @@ -2074,7 +2050,7 @@ dependencies = [ "digest", "endi", "futures-util", - "getrandom 0.3.1", + "getrandom 0.3.3", "hkdf", "hmac", "md-5", @@ -2299,9 +2275,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plain" @@ -2319,7 +2295,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", + "rustix 0.38.44", "tracing", "windows-sys 0.59.0", ] @@ -2347,6 +2323,15 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2355,49 +2340,55 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" -version = "0.37.2" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.8.5" @@ -2445,7 +2436,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -2454,7 +2445,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.3", ] [[package]] @@ -2465,9 +2456,9 @@ checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redox_syscall" -version = "0.5.9" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ "bitflags", ] @@ -2478,7 +2469,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror 2.0.12", ] @@ -2567,21 +2558,34 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "salsa20" @@ -2600,9 +2604,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scratch" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" +checksum = "9f6280af86e5f559536da57a45ebc84948833b3bee313a7dd25232e09c878a52" [[package]] name = "scroll" @@ -2615,9 +2619,9 @@ dependencies = [ [[package]] name = "scroll_derive" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", @@ -2660,9 +2664,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" dependencies = [ "serde", ] @@ -2701,9 +2705,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", @@ -2740,9 +2744,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -2785,9 +2789,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "smawk" @@ -2797,9 +2801,9 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2894,9 +2898,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.98" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -2916,9 +2920,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.35.0" +version = "0.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b897c8ea620e181c7955369a31be5f48d9a9121cb59fd33ecef9ff2a34323422" +checksum = "79251336d17c72d9762b8b54be4befe38d2db56fbbc0241396d70f173c39d47a" dependencies = [ "libc", "memchr", @@ -2930,15 +2934,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.17.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ - "cfg-if", "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.3", "once_cell", - "rustix", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -2953,9 +2956,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ "smawk", ] @@ -3002,9 +3005,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -3019,15 +3022,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -3035,9 +3038,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -3107,15 +3110,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", "toml_datetime", @@ -3191,9 +3194,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-segmentation" @@ -3203,9 +3206,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.14" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "uniffi" @@ -3359,12 +3362,6 @@ dependencies = [ "serde", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -3391,43 +3388,43 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wayland-backend" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" dependencies = [ "cc", "downcast-rs", - "rustix", + "rustix 0.38.44", "smallvec", "wayland-sys", ] [[package]] name = "wayland-client" -version = "0.31.8" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" dependencies = [ "bitflags", - "rustix", + "rustix 0.38.44", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-protocols" -version = "0.32.6" +version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc" +checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" dependencies = [ "bitflags", "wayland-backend", @@ -3437,9 +3434,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248a02e6f595aad796561fa82d25601bd2c8c3b145b1c7453fc8f94c1a58f8b2" +checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" dependencies = [ "bitflags", "wayland-backend", @@ -3829,18 +3826,18 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] @@ -3854,7 +3851,7 @@ dependencies = [ "libc", "log", "os_pipe", - "rustix", + "rustix 0.38.44", "tempfile", "thiserror 1.0.69", "tree_magic_mini", @@ -3864,17 +3861,11 @@ dependencies = [ "wayland-protocols-wlr", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "x11rb" @@ -3883,7 +3874,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ "gethostname", - "rustix", + "rustix 0.38.44", "x11rb-protocol", ] @@ -3905,9 +3896,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -3917,9 +3908,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -4059,19 +4050,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", @@ -4126,10 +4116,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebff5e6b81c1c7dca2d0bd333b2006da48cb37dbcae5a8da888f31fcb3c19934" [[package]] -name = "zerovec" -version = "0.10.4" +name = "zerotrie" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -4138,9 +4139,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index 8b39fd9805e..37b8cf96ff3 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -110,9 +110,9 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "license": "MIT", "bin": { "acorn": "bin/acorn" diff --git a/package-lock.json b/package-lock.json index bd2cabe1cfe..579ff539de3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -357,9 +357,9 @@ "license": "GPL-3.0" }, "node_modules/@adobe/css-tools": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz", - "integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz", + "integrity": "sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==", "dev": true, "license": "MIT" }, @@ -398,14 +398,14 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1901.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1901.8.tgz", - "integrity": "sha512-DzvlL1Zg+zOnVmMN3CjE5KzjZAltRZwOwwcso72iWenBPvl/trKzPDlA6ySmpRonm+AR9i9JrdLEUlwczW6/bQ==", + "version": "0.1902.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.12.tgz", + "integrity": "sha512-LfUc7k84YL290hAxsG+FvjQpXugQXyw5aDzrQQB4iTYhBgaABu2aaNOU4eu3JH+F8NeXd2EBF/YMr2LDSkYlMw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@angular-devkit/core": "19.1.8", + "@angular-devkit/core": "19.2.12", "rxjs": "7.8.1" }, "engines": { @@ -626,19 +626,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/@babel/plugin-syntax-import-attributes": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", @@ -750,9 +737,9 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz", + "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==", "dev": true, "license": "MIT", "dependencies": { @@ -841,20 +828,6 @@ "webpack": ">=5" } }, - "node_modules/@angular-devkit/build-angular/node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", - "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3", - "core-js-compat": "^3.40.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/browserslist": { "version": "4.24.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", @@ -913,6 +886,19 @@ "fsevents": "~2.3.2" } }, + "node_modules/@angular-devkit/build-angular/node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -945,19 +931,6 @@ "webpack": "^5.1.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/copy-webpack-plugin/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/define-lazy-prop": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", @@ -1016,19 +989,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@angular-devkit/build-angular/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/https-proxy-agent": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", @@ -1618,9 +1578,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "19.1.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.1.8.tgz", - "integrity": "sha512-j1zHKvOsGwu5YwAZGuzi835R9vcW7PkfxmSRIJeVl+vawgk31K3zFb4UPH8AY/NPWYqXIAnwpka3HC1+JrWLWA==", + "version": "19.2.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz", + "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==", "dev": true, "license": "MIT", "peer": true, @@ -1781,13 +1741,13 @@ } }, "node_modules/@angular-eslint/builder/node_modules/@angular-devkit/architect": { - "version": "0.1802.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.14.tgz", - "integrity": "sha512-eplaGCXSlPwf1f4XwyzsYTd8/lJ0/Adm6XsODsBxvkZlIpLcps80/h2lH5MVJpoDREzIFu1BweDpYCoNK5yYZg==", + "version": "0.1802.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz", + "integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.14", + "@angular-devkit/core": "18.2.19", "rxjs": "7.8.1" }, "engines": { @@ -1797,9 +1757,9 @@ } }, "node_modules/@angular-eslint/builder/node_modules/@angular-devkit/core": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.14.tgz", - "integrity": "sha512-UGIGOjXuOyCW+5S4tINu7e6LOu738CmTw3h7Ui1I8OzdTIYJcYJrei8sgrwDwOYADRal+p0MeMlnykH3TM5XBA==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1955,9 +1915,9 @@ } }, "node_modules/@angular-eslint/schematics/node_modules/@angular-devkit/core": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.14.tgz", - "integrity": "sha512-UGIGOjXuOyCW+5S4tINu7e6LOu738CmTw3h7Ui1I8OzdTIYJcYJrei8sgrwDwOYADRal+p0MeMlnykH3TM5XBA==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2253,6 +2213,19 @@ "semver": "bin/semver.js" } }, + "node_modules/@angular/build/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@angular/build/node_modules/agent-base": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", @@ -2746,13 +2719,13 @@ } }, "node_modules/@asamuzakjp/css-color": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-2.8.3.tgz", - "integrity": "sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", "license": "MIT", "dependencies": { - "@csstools/css-calc": "^2.1.1", - "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "lru-cache": "^10.4.3" @@ -2779,9 +2752,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", + "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", "dev": true, "license": "MIT", "engines": { @@ -2854,27 +2827,27 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", - "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -2884,9 +2857,9 @@ } }, "node_modules/@babel/helper-compilation-targets/node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", "dev": true, "funding": [ { @@ -2904,10 +2877,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -2927,18 +2900,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz", - "integrity": "sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", + "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.26.9", + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", "semver": "^6.3.1" }, "engines": { @@ -2949,13 +2922,13 @@ } }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", + "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3013,9 +2986,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", - "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", + "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", "dev": true, "license": "MIT", "dependencies": { @@ -3030,42 +3003,42 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", + "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3075,13 +3048,13 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3098,15 +3071,15 @@ } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", - "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-wrap-function": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3116,28 +3089,28 @@ } }, "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", + "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", - "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.26.5" + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3147,14 +3120,14 @@ } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3192,9 +3165,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -3202,15 +3175,15 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", - "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", + "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3246,14 +3219,14 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", - "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3263,13 +3236,13 @@ } }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", - "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3279,13 +3252,13 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", - "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3295,15 +3268,15 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3313,14 +3286,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", - "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", + "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3424,13 +3397,13 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", - "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3482,13 +3455,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", - "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3608,13 +3581,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", - "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3641,13 +3614,13 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", - "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3693,13 +3666,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", - "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3709,13 +3682,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", - "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.1.tgz", + "integrity": "sha512-QEcFlMl9nGTgh1rn2nIeU5bkfb9BAjaQcWbiP4LvKxUot52ABcTkpcyJ7f2Q2U2RuQ84BNLgts3jRme2dTx6Fw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3725,14 +3698,14 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", - "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3742,14 +3715,14 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", - "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", + "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3759,17 +3732,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", - "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz", + "integrity": "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/traverse": "^7.25.9", + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.27.1", "globals": "^11.1.0" }, "engines": { @@ -3780,27 +3753,27 @@ } }, "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", + "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", - "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/template": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3810,13 +3783,13 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", - "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.1.tgz", + "integrity": "sha512-ttDCqhfvpE9emVkXbPD8vyxxh4TWYACVybGkDj+oReOGwnp066ITEivDlLwe0b1R0+evJ13IXQuLNB5w1fhC5Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3826,14 +3799,14 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", - "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3843,13 +3816,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", - "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3859,14 +3832,14 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3876,13 +3849,13 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", - "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3892,13 +3865,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", - "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3908,13 +3881,13 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", - "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3924,14 +3897,14 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz", - "integrity": "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3941,15 +3914,15 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", - "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3959,13 +3932,13 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", - "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3975,13 +3948,13 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", - "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3991,13 +3964,13 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", - "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4007,13 +3980,13 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", - "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4023,14 +3996,14 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", - "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4040,14 +4013,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", - "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4057,16 +4030,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", - "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4076,14 +4049,14 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", - "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4093,14 +4066,14 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4110,13 +4083,13 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", - "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4126,13 +4099,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.26.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", - "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4142,13 +4115,13 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", - "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4158,15 +4131,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", - "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.2.tgz", + "integrity": "sha512-AIUHD7xJ1mCrj3uPozvtngY3s0xpv7Nu7DoUSnzNY6Xam1Cy4rUznR//pvMHOhQ4AvbCexhbqXCtpxGHOGOO6g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9" + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4176,14 +4150,14 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", - "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4193,13 +4167,13 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", - "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4209,14 +4183,14 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4226,13 +4200,13 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", - "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz", + "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4242,14 +4216,14 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", - "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4259,15 +4233,15 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", - "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4277,26 +4251,26 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", + "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", - "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4306,14 +4280,13 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", - "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.1.tgz", + "integrity": "sha512-B19lbbL7PMrKr52BNPjCqg1IyNUIjTcxKj8uX9zHO+PmWN93s19NDr/f69mIkEp2x9nmDJ08a7lgHaTTzvW7mw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "regenerator-transform": "^0.15.2" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4340,13 +4313,13 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", - "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4376,20 +4349,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-runtime/node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", - "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3", - "core-js-compat": "^3.40.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -4401,13 +4360,13 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", - "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4417,14 +4376,14 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", - "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4434,13 +4393,13 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", - "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4450,13 +4409,13 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz", - "integrity": "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4466,13 +4425,13 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz", - "integrity": "sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4482,13 +4441,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", - "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4498,14 +4457,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", - "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4515,14 +4474,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", - "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4532,14 +4491,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", - "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4644,6 +4603,20 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@babel/preset-env/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -5082,13 +5055,13 @@ } }, "node_modules/@compodoc/compodoc/node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", - "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -5207,6 +5180,20 @@ "semver": "bin/semver.js" } }, + "node_modules/@compodoc/compodoc/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@compodoc/compodoc/node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -5431,9 +5418,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.2.tgz", - "integrity": "sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.3.tgz", + "integrity": "sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==", "funding": [ { "type": "github", @@ -5454,9 +5441,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.8.tgz", - "integrity": "sha512-pdwotQjCCnRPuNi06jFuP68cykU1f3ZWExLe/8MQ1LOs8Xq+fTkYgd+2V8mWUWMrOn9iS2HftPVaMZDaXzGbhQ==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.9.tgz", + "integrity": "sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==", "funding": [ { "type": "github", @@ -5470,7 +5457,7 @@ "license": "MIT", "dependencies": { "@csstools/color-helpers": "^5.0.2", - "@csstools/css-calc": "^2.1.2" + "@csstools/css-calc": "^2.1.3" }, "engines": { "node": ">=18" @@ -5584,9 +5571,9 @@ } }, "node_modules/@electron/asar": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.3.1.tgz", - "integrity": "sha512-WtpC/+34p0skWZiarRjLAyqaAX78DofhDxnREy/V5XHfu1XEXbFCSSMcDQ6hNCPJFaPy8/NnUgYuf9uiCkvKPg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", "dev": true, "license": "MIT", "dependencies": { @@ -6020,29 +6007,32 @@ } }, "node_modules/@emnapi/core": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.1.tgz", - "integrity": "sha512-4JFstCTaToCFrPqrGzgkF8N2NHjtsaY4uRh6brZQ5L9e4wbMieX8oDT8N7qfVFTQecHFEtkj4ve49VIZ3mKVqw==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", + "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", "dev": true, + "license": "MIT", "dependencies": { - "@emnapi/wasi-threads": "1.0.1", + "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.1.tgz", - "integrity": "sha512-LMshMVP0ZhACNjQNYXiU1iZJ6QCcv0lUdPDPugqGvCGXt5xtRVBPdtA0qU12pEXZzpWAhWlZYptfdAFq10DOVQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.4.0" } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.1.tgz", - "integrity": "sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", + "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.4.0" } @@ -6559,9 +6549,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, "license": "MIT", "dependencies": { @@ -6763,9 +6753,9 @@ } }, "node_modules/@figspec/react": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@figspec/react/-/react-1.0.3.tgz", - "integrity": "sha512-r683qOko+5CbT48Ox280fMx2MNAtaFPgCNJvldOqN3YtmAzlcTT+YSxd3OahA+kjXGGrnzDbUgeTOX1cPLII+g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@figspec/react/-/react-1.0.4.tgz", + "integrity": "sha512-jaPvkIef4d6NjsRiw91OZabrfdPH9FtoPGYcY5mpXjYEcdUqIq1aHtLq3SkMVyVysEapTEJ6yS8amy93MyXBEQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6773,7 +6763,7 @@ "@lit-labs/react": "^1.0.2" }, "peerDependencies": { - "react": "^16.14.0 || ^17.0.0 || ^18.0.0" + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/@gar/promisify": { @@ -6967,9 +6957,9 @@ } }, "node_modules/@inquirer/figures": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.10.tgz", - "integrity": "sha512-Ey6176gZmeqZuY/W/nZiUyvmb1/qInjcpiZjXWi6nON+nxJpD1bxtSoBxNliGISae32n6OwbY+TSXPZ1CfS4bw==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.11.tgz", + "integrity": "sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==", "dev": true, "license": "MIT", "engines": { @@ -7347,16 +7337,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/console/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/core": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", @@ -7440,16 +7420,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@jest/core/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/create-cache-key-function": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", @@ -7630,16 +7600,6 @@ "node": "*" } }, - "node_modules/@jest/reporters/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -7700,16 +7660,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/test-sequencer/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/transform": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", @@ -7744,16 +7694,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@jest/transform/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/types": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", @@ -7849,9 +7789,9 @@ } }, "node_modules/@jsonjoy.com/json-pack": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.1.tgz", - "integrity": "sha512-osjeBqMJ2lb/j/M8NCPjs1ylqWIcTRTycIhVB5pt6LgzgeRSb0YRZ7j9RfA8wIUrsr/medIuhVyonXRZWLyfdw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz", + "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -7872,9 +7812,9 @@ } }, "node_modules/@jsonjoy.com/util": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", - "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.6.0.tgz", + "integrity": "sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==", "dev": true, "license": "Apache-2.0", "engines": { @@ -7962,9 +7902,9 @@ "license": "BSD-3-Clause" }, "node_modules/@lit/reactive-element": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz", - "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.0.tgz", + "integrity": "sha512-L2qyoZSQClcBmq0qajBVbhYEcG6iK0XfLn66ifLe/RfC0/ihpc+pl0Wdn8bJ8o+hj38cG0fGXRgSS20MuXn7qA==", "license": "BSD-3-Clause", "dependencies": { "@lit-labs/ssr-dom-shim": "^1.2.0" @@ -8301,6 +8241,7 @@ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz", "integrity": "sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==", "dev": true, + "license": "MIT", "dependencies": { "@emnapi/core": "^1.1.0", "@emnapi/runtime": "^1.1.0", @@ -8840,6 +8781,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -8856,6 +8798,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -8872,6 +8815,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -8888,6 +8832,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -8904,6 +8849,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -8920,6 +8866,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -8936,6 +8883,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -8952,6 +8900,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -8968,6 +8917,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -8984,6 +8934,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -10699,9 +10650,9 @@ } }, "node_modules/@storybook/csf": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.12.tgz", - "integrity": "sha512-9/exVhabisyIVL0VxTCxo01Tdm8wefIXKXfltAPTSr8cbLn5JAxGQ6QV3mjdecLGEOucfoVhAKtJfVHxEK1iqw==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.13.tgz", + "integrity": "sha512-7xOOwCLGB3ebM87eemep89MYRFTko+D8qE7EdAAq74lgdqRR5cOUtYWJLjO2dLtP94nqoOdHJo6MdLLKzg412Q==", "dev": true, "license": "MIT", "dependencies": { @@ -10733,9 +10684,9 @@ "license": "MIT" }, "node_modules/@storybook/icons": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-1.3.2.tgz", - "integrity": "sha512-t3xcbCKkPvqyef8urBM0j/nP6sKtnlRkVgC+8JTbTAZQjaTmOjes3byEgzs89p4B/K6cJsg9wLW2k3SknLtYJw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-1.4.0.tgz", + "integrity": "sha512-Td73IeJxOyalzvjQL+JXx72jlIYHgs+REaHiREOqfpo3A2AYYG71AUbcv+lg7mEDIweKVCxsMQ0UKo634c8XeA==", "dev": true, "license": "MIT", "engines": { @@ -11361,6 +11312,7 @@ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.4.0" } @@ -11404,9 +11356,9 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, "license": "MIT", "dependencies": { @@ -11425,9 +11377,9 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", - "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", "dev": true, "license": "MIT", "dependencies": { @@ -11560,15 +11512,14 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", - "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.2.tgz", + "integrity": "sha512-BtjL3ZwbCQriyb0DGw+Rt12qAXPiBTPs815lsUvtt1Grk0vLRMZNMUZ741d5rjk+UQOxfDiBZ3dxpX00vSkK3g==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", - "@types/qs": "*", "@types/serve-static": "*" } }, @@ -11876,9 +11827,9 @@ } }, "node_modules/@types/lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==", + "version": "4.17.16", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz", + "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==", "dev": true, "license": "MIT" }, @@ -12033,9 +11984,9 @@ } }, "node_modules/@types/qs": { - "version": "6.9.18", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", - "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "dev": true, "license": "MIT" }, @@ -12058,9 +12009,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.3.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", - "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -12085,9 +12036,9 @@ "license": "MIT" }, "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", "dev": true, "license": "MIT" }, @@ -12211,9 +12162,9 @@ "license": "MIT" }, "node_modules/@types/ws": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz", - "integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "license": "MIT", "dependencies": { @@ -12475,16 +12426,6 @@ "node": ">= 4" } }, - "node_modules/@typescript-eslint/experimental-utils/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@typescript-eslint/parser": { "version": "8.31.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz", @@ -12631,9 +12572,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.30.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.30.1.tgz", - "integrity": "sha512-81KawPfkuulyWo5QdyG/LOKbspyyiW+p4vpn4bYO7DM/hZImlVnFwrpCTnmNMOt8CvLRr5ojI9nU1Ekpw4RcEw==", + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", + "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", "dev": true, "license": "MIT", "engines": { @@ -12975,15 +12916,15 @@ } }, "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.9.tgz", - "integrity": "sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.10.tgz", + "integrity": "sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.4.0", - "@emnapi/runtime": "^1.4.0", + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.9.0" } }, @@ -13495,6 +13436,7 @@ "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.2.tgz", "integrity": "sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "js-yaml": "^3.10.0", "tslib": "^2.4.0" @@ -13508,6 +13450,7 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } @@ -13517,6 +13460,7 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -13529,13 +13473,15 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@zkochan/js-yaml": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.7.tgz", "integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -13591,9 +13537,9 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", "bin": { @@ -13788,9 +13734,9 @@ } }, "node_modules/angular-eslint/node_modules/@angular-devkit/core": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.14.tgz", - "integrity": "sha512-UGIGOjXuOyCW+5S4tINu7e6LOu738CmTw3h7Ui1I8OzdTIYJcYJrei8sgrwDwOYADRal+p0MeMlnykH3TM5XBA==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14390,18 +14336,19 @@ } }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -14655,9 +14602,9 @@ } }, "node_modules/axe-core": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", - "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", "dev": true, "license": "MPL-2.0", "engines": { @@ -14698,10 +14645,11 @@ } }, "node_modules/axios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", - "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", "dev": true, + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -14740,16 +14688,6 @@ "@babel/core": "^7.8.0" } }, - "node_modules/babel-jest/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/babel-loader": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", @@ -14878,14 +14816,14 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.12", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", - "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", + "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.3", + "@babel/helper-define-polyfill-provider": "^0.6.4", "semver": "^6.3.1" }, "peerDependencies": { @@ -14903,27 +14841,27 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", - "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2", - "core-js-compat": "^3.38.0" + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", - "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", + "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3" + "@babel/helper-define-polyfill-provider": "^0.6.4" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -15898,13 +15836,13 @@ } }, "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -15963,9 +15901,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001717", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz", - "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==", + "version": "1.0.30001718", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", + "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", "dev": true, "funding": [ { @@ -16271,9 +16209,9 @@ } }, "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", + "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", "license": "MIT", "engines": { "node": ">=6" @@ -16985,13 +16923,13 @@ } }, "node_modules/core-js-compat": { - "version": "3.40.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz", - "integrity": "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==", + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz", + "integrity": "sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.24.3" + "browserslist": "^4.24.4" }, "funding": { "type": "opencollective", @@ -16999,9 +16937,9 @@ } }, "node_modules/core-js-compat/node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", "dev": true, "funding": [ { @@ -17019,10 +16957,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -17322,12 +17260,12 @@ "license": "MIT" }, "node_modules/cssstyle": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.2.1.tgz", - "integrity": "sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.3.1.tgz", + "integrity": "sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q==", "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^2.8.2", + "@asamuzakjp/css-color": "^3.1.2", "rrweb-cssom": "^0.8.0" }, "engines": { @@ -17438,9 +17376,9 @@ } }, "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==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -17484,9 +17422,9 @@ "license": "MIT" }, "node_modules/decode-named-character-reference": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", - "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz", + "integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==", "dev": true, "license": "MIT", "dependencies": { @@ -17527,9 +17465,9 @@ } }, "node_modules/dedent": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", - "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -17754,9 +17692,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -18207,21 +18145,33 @@ } }, "node_modules/dotenv": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", - "integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==", + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "dev": true, "license": "BSD-2-Clause", "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/dotenv-expand": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", - "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", "dev": true, - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } }, "node_modules/dunder-proto": { "version": "1.0.1", @@ -18494,9 +18444,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.151", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz", - "integrity": "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==", + "version": "1.5.155", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", + "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==", "dev": true, "license": "ISC" }, @@ -18547,9 +18497,9 @@ } }, "node_modules/electron/node_modules/@types/node": { - "version": "20.17.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.19.tgz", - "integrity": "sha512-LEwC7o1ifqg/6r2gn9Dns0f1rhK+fPFDoMiceTJ6kWmVk6bgXBI/9IOWfVan4WiAavK9pIVWdX0/e3J+eEUh5A==", + "version": "20.17.48", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.48.tgz", + "integrity": "sha512-KpSfKOHPsiSC4IkZeu2LsusFwExAIVGkhG1KkbaBMLwau0uMhj0fCrvyg9ddM2sAvd+gtiBJLir4LAw1MNMIaw==", "dev": true, "license": "MIT", "dependencies": { @@ -18649,6 +18599,7 @@ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-colors": "^4.1.1" }, @@ -18832,9 +18783,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", - "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, "license": "MIT" }, @@ -19375,9 +19326,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -20107,9 +20058,9 @@ } }, "node_modules/fastq": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", - "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, "license": "ISC", "dependencies": { @@ -20910,6 +20861,7 @@ "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", "dev": true, + "license": "MIT", "dependencies": { "js-yaml": "^3.13.1" } @@ -20919,6 +20871,7 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } @@ -20928,6 +20881,7 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -20940,7 +20894,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/fs-constants": { "version": "1.0.0", @@ -21210,9 +21165,9 @@ "license": "MIT" }, "node_modules/glob": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", - "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz", + "integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==", "dev": true, "license": "ISC", "dependencies": { @@ -21416,15 +21371,28 @@ } }, "node_modules/globby/node_modules/ignore": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz", - "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", + "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", "dev": true, "license": "MIT", "engines": { "node": ">= 4" } }, + "node_modules/globby/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -21763,9 +21731,9 @@ } }, "node_modules/html-entities": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", - "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", "dev": true, "funding": [ { @@ -22006,9 +21974,9 @@ } }, "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "dev": true, "license": "BSD-2-Clause" }, @@ -22045,9 +22013,9 @@ } }, "node_modules/http-parser-js": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz", - "integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", "dev": true, "license": "MIT" }, @@ -22320,9 +22288,9 @@ "license": "MIT" }, "node_modules/immutable": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz", - "integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz", + "integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==", "dev": true, "license": "MIT" }, @@ -22725,9 +22693,9 @@ } }, "node_modules/is-bun-module/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "license": "ISC", "bin": { @@ -23668,16 +23636,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jest-circus/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-cli": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", @@ -23839,16 +23797,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jest-config/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-diff": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", @@ -24422,16 +24370,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jest-message-util/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-mock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", @@ -24728,16 +24666,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-resolve/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-runner": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", @@ -24872,16 +24800,6 @@ "node": "*" } }, - "node_modules/jest-runtime/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-serializer-html": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/jest-serializer-html/-/jest-serializer-html-7.1.0.tgz", @@ -25127,6 +25045,19 @@ "node": ">=12.20" } }, + "node_modules/jest-watch-typeahead/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/jest-watch-typeahead/node_modules/string-length": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", @@ -25414,13 +25345,13 @@ "license": "BSD-2-Clause" }, "node_modules/json-stable-stringify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.2.1.tgz", - "integrity": "sha512-Lp6HbbBgosLmJbjx0pBLbgvx68FaFU1sdkmBuckmhhJ88kL13OA51CDtR2yJB50eCNMH9wRqtQNNiAqQH4YXnA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", - "call-bound": "^1.0.3", + "call-bound": "^1.0.4", "isarray": "^2.0.5", "jsonify": "^0.0.1", "object-keys": "^1.1.1" @@ -26029,10 +25960,14 @@ } }, "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", + "integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } }, "node_modules/lint-staged": { "version": "15.5.1", @@ -26216,9 +26151,9 @@ } }, "node_modules/lint-staged/node_modules/listr2": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.2.tgz", - "integrity": "sha512-vsBzcU4oE+v0lj4FhVLzr9dBTv4/fHIa57l+GCwovP8MoFNZJTOhGU8PXd4v2VJCbECAaijBiHntiekFMLvo0g==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", + "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", "dev": true, "license": "MIT", "dependencies": { @@ -26542,20 +26477,20 @@ } }, "node_modules/lit-element": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.1.tgz", - "integrity": "sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.0.tgz", + "integrity": "sha512-MGrXJVAI5x+Bfth/pU9Kst1iWID6GHDLEzFEnyULB/sFiRLgkd8NPK/PeeXxktA3T6EIIaq8U3KcbTU5XFcP2Q==", "license": "BSD-3-Clause", "dependencies": { "@lit-labs/ssr-dom-shim": "^1.2.0", - "@lit/reactive-element": "^2.0.4", - "lit-html": "^3.2.0" + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" } }, "node_modules/lit-html": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.1.tgz", - "integrity": "sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.0.tgz", + "integrity": "sha512-RHoswrFAxY2d8Cf2mm4OZ1DgzCoBKUKSPvA1fhtSELxUERq2aQQ2h05pO9j81gS1o7RIRJ+CePLogfyahwmynw==", "license": "BSD-3-Clause", "dependencies": { "@types/trusted-types": "^2.0.2" @@ -27524,9 +27459,9 @@ } }, "node_modules/micromark": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", - "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", "dev": true, "funding": [ { @@ -27560,9 +27495,9 @@ } }, "node_modules/micromark-core-commonmark": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", - "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", "dev": true, "funding": [ { @@ -28058,9 +27993,9 @@ } }, "node_modules/micromark-util-subtokenize": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.4.tgz", - "integrity": "sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", "dev": true, "funding": [ { @@ -28098,9 +28033,9 @@ "license": "MIT" }, "node_modules/micromark-util-types": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", - "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", "dev": true, "funding": [ { @@ -28564,9 +28499,9 @@ "license": "MIT" }, "node_modules/msgpackr": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.2.tgz", - "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==", + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.4.tgz", + "integrity": "sha512-uaff7RG9VIC4jacFW9xzL3jc0iM32DNHe4jYVycBcjUePT/Klnfj7pqtWJt9khvDFizmjN2TlYniYmSS2LIaZg==", "dev": true, "license": "MIT", "optionalDependencies": { @@ -28698,9 +28633,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -28724,9 +28659,9 @@ "license": "MIT" }, "node_modules/napi-postinstall": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.2.tgz", - "integrity": "sha512-Wy1VI/hpKHwy1MsnFxHCJxqFwmmxD0RA/EKPL7e6mfbsY01phM2SZyJnRdU0bLvhu0Quby1DCcAZti3ghdl4/A==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.4.tgz", + "integrity": "sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==", "dev": true, "license": "MIT", "bin": { @@ -28840,9 +28775,9 @@ } }, "node_modules/node-abi": { - "version": "3.74.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", - "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", + "version": "3.75.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", + "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", "dev": true, "license": "MIT", "dependencies": { @@ -28869,9 +28804,9 @@ } }, "node_modules/node-api-version": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.0.tgz", - "integrity": "sha512-fthTTsi8CxaBXMaBAD7ST2uylwvsnYxh2PfaScwpMhos6KlSFajXQPcM4ogNE1q2s3Lbz9GCGqeIHC+C6OZnKg==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -29258,7 +29193,8 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/node-preload": { "version": "0.2.1", @@ -29737,9 +29673,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.16", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", - "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", + "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", "license": "MIT" }, "node_modules/nx": { @@ -29748,6 +29684,7 @@ "integrity": "sha512-+BN5B5DFBB5WswD8flDDTnr4/bf1VTySXOv60aUAllHqR+KS6deT0p70TTMZF4/A2n/L2UCWDaDro37MGaYozA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -29813,50 +29750,12 @@ } } }, - "node_modules/nx/node_modules/cli-spinners": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", - "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nx/node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/nx/node_modules/dotenv-expand": { - "version": "11.0.7", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", - "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", - "dev": true, - "dependencies": { - "dotenv": "^16.4.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/nx/node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -29865,22 +29764,15 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "node_modules/nx/node_modules/lines-and-columns": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", - "integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==", "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } + "license": "MIT" }, "node_modules/nx/node_modules/minimatch": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -29896,6 +29788,7 @@ "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz", "integrity": "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==", "dev": true, + "license": "MIT", "dependencies": { "bl": "^4.0.3", "chalk": "^4.1.0", @@ -29918,6 +29811,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -29927,6 +29821,7 @@ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.14" } @@ -29936,6 +29831,7 @@ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, + "license": "MIT", "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", @@ -31012,6 +30908,12 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "license": "MIT" }, + "node_modules/parse-json/node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/parse-node-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", @@ -31291,9 +31193,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", - "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", "dev": true, "license": "ISC", "engines": { @@ -31391,9 +31293,9 @@ } }, "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, "license": "MIT", "engines": { @@ -31502,9 +31404,9 @@ } }, "node_modules/pkg-dir/node_modules/yocto-queue": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", - "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", "dev": true, "license": "MIT", "engines": { @@ -32128,9 +32030,9 @@ } }, "node_modules/prismjs": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", - "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", "dev": true, "license": "MIT", "engines": { @@ -32587,6 +32489,23 @@ "node": ">=12.0.0" } }, + "node_modules/read-config-file/node_modules/dotenv": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", + "integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/read-config-file/node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -32641,9 +32560,9 @@ } }, "node_modules/recast": { - "version": "0.23.9", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.9.tgz", - "integrity": "sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q==", + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", "dev": true, "license": "MIT", "dependencies": { @@ -32750,16 +32669,6 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "license": "MIT" }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, "node_modules/regex-parser": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.1.tgz", @@ -33253,9 +33162,9 @@ } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -33732,20 +33641,19 @@ "optional": true }, "node_modules/send": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", - "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "dev": true, "license": "MIT", "dependencies": { "debug": "^4.3.5", - "destroy": "^1.2.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", - "fresh": "^0.5.2", + "fresh": "^2.0.0", "http-errors": "^2.0.0", - "mime-types": "^2.1.35", + "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", @@ -33765,6 +33673,39 @@ "node": ">= 0.8" } }, + "node_modules/send/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/send/node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -34317,16 +34258,13 @@ "license": "MIT" }, "node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/slice-ansi": { @@ -35236,6 +35174,13 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/sucrase/node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/sucrase/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -35327,11 +35272,15 @@ "license": "MIT" }, "node_modules/tablesort": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/tablesort/-/tablesort-5.3.0.tgz", - "integrity": "sha512-WkfcZBHsp47gVH9CBHG0ZXopriG01IA87arGrchvIe868d4RiXVvoYPS1zMq9IdW05kBs5iGsqxTABqLyWonbg==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/tablesort/-/tablesort-5.6.0.tgz", + "integrity": "sha512-cZZXK3G089PbpxH8N7vN7Z21SEKqXAaCiSVOmZdR/v7z8TFCsF/OFr0rzjhQuFlQQHy9uQtW9P2oQFJzJFGVrg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 16", + "npm": ">= 8" + } }, "node_modules/tailwindcss": { "version": "3.4.17", @@ -35450,9 +35399,9 @@ } }, "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", "dev": true, "license": "MIT", "engines": { @@ -35587,9 +35536,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", - "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", "dev": true, "license": "MIT", "dependencies": { @@ -35840,9 +35789,9 @@ } }, "node_modules/tldts-core": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.4.tgz", - "integrity": "sha512-9/IRbnIvUENGD6rg7m6Q9h/jH5ZL28hwjAhxrJx0AmcBue1FSsc84XZFaV748EsDVflid86aGDR11eSz6sbQjA==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.7.tgz", + "integrity": "sha512-ECqb8imSroX1UmUuhRBNPkkmtZ8mHEenieim80UVxG0M5wXVjY2Fp2tYXCPvk+nLy1geOhFpeD5YQhM/gF63Jg==", "license": "MIT" }, "node_modules/tmp": { @@ -35930,9 +35879,9 @@ } }, "node_modules/tr46": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", - "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "license": "MIT", "dependencies": { "punycode": "^2.3.1" @@ -35942,9 +35891,9 @@ } }, "node_modules/tree-dump": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", - "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz", + "integrity": "sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -35990,9 +35939,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", - "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, "license": "MIT", "engines": { @@ -36647,6 +36596,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -38173,9 +38123,9 @@ } }, "node_modules/webpack-dev-middleware/node_modules/memfs": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.0.tgz", - "integrity": "sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==", + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz", + "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -38263,9 +38213,9 @@ } }, "node_modules/webpack-dev-server/node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz", + "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==", "dev": true, "license": "MIT", "dependencies": { @@ -38340,9 +38290,9 @@ } }, "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", - "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -38394,9 +38344,9 @@ } }, "node_modules/webpack-dev-server/node_modules/open": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", - "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", + "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==", "dev": true, "license": "MIT", "dependencies": { @@ -38522,9 +38472,9 @@ "license": "MIT" }, "node_modules/webpack/node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", "dev": true, "funding": [ { @@ -38542,10 +38492,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -38632,12 +38582,12 @@ } }, "node_modules/whatwg-url": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz", - "integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "license": "MIT", "dependencies": { - "tr46": "^5.0.0", + "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" }, "engines": { @@ -38734,16 +38684,17 @@ "license": "ISC" }, "node_modules/which-typed-array": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", - "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "for-each": "^0.3.3", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" }, @@ -38896,9 +38847,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -38975,15 +38926,15 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", - "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" } }, "node_modules/yargs": { diff --git a/package.json b/package.json index 9831417de59..b1e91d92771 100644 --- a/package.json +++ b/package.json @@ -211,6 +211,7 @@ "@storybook/angular": { "zone.js": "$zone.js" }, + "parse5": "7.2.1", "react": "18.3.1", "react-dom": "18.3.1", "@types/react": "18.3.20", From 2e4b310137ac4b24128c185420de42a7d93b1d76 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 May 2025 11:04:35 +0200 Subject: [PATCH 125/245] [deps] Platform: Pin dependencies (#14446) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 10 +++++----- apps/desktop/desktop_native/Cargo.toml | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 242c1e1b9f2..e5d90446ddc 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -498,9 +498,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "camino" @@ -1598,7 +1598,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", - "windows-targets 0.53.0", + "windows-targets 0.52.6", ] [[package]] @@ -2920,9 +2920,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.35.1" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79251336d17c72d9762b8b54be4befe38d2db56fbbc0241396d70f173c39d47a" +checksum = "b897c8ea620e181c7955369a31be5f48d9a9121cb59fd33ecef9ff2a34323422" dependencies = [ "libc", "memchr", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 81149164253..71da53c867d 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -14,10 +14,10 @@ anyhow = "=1.0.94" arboard = { version = "=3.5.0", default-features = false } argon2 = "=0.5.3" base64 = "=0.22.1" -bindgen = "0.71.1" +bindgen = "=0.71.1" bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "3d48f140fd506412d186203238993163a8c4e536" } byteorder = "=1.5.0" -bytes = "1.9.0" +bytes = "=1.9.0" cbc = "=0.1.2" core-foundation = "=0.10.0" dirs = "=6.0.0" @@ -49,7 +49,7 @@ sha2 = "=0.10.8" simplelog = "=0.12.2" ssh-encoding = "=0.2.0" ssh-key = {version = "=0.6.7", default-features = false } -sysinfo = "0.35.0" +sysinfo = "=0.35.0" thiserror = "=2.0.12" tokio = "=1.45.0" tokio-stream = "=0.1.15" From ae35cb4e65af6e068cd0504272f0828cba763d1b Mon Sep 17 00:00:00 2001 From: Ike <137194738+ike-kottlowski@users.noreply.github.com> Date: Wed, 21 May 2025 08:24:17 -0400 Subject: [PATCH 126/245] [PM-20540] Deep-link refactor to fix SSO deep links (#14587) * PM-20540 - TwoFactorAuthComponent - Refactor determineDefaultSuccessRoute to rely on user's auth status as the loginStrategyService's state is cleared after successful AuthN * PM-20540 - DeepLinkGuard - Refactor to exempt login-initiated so that TDE + unlock with MP + deep link works. * doc: Add documentation and change folder structure. * test: add test for new excluded route. --------- Co-authored-by: Jared Snider --- .../organization-routing.module.ts | 2 +- .../src/app/auth/guards/deep-link.guard.ts | 58 ----------- .../{ => deep-link}/deep-link.guard.spec.ts | 14 ++- .../auth/guards/deep-link/deep-link.guard.ts | 99 +++++++++++++++++++ .../src/app/auth/guards/deep-link/readme.md | 23 +++++ apps/web/src/app/oss-routing.module.ts | 2 +- .../organizations-routing.module.ts | 2 +- .../bit-web/src/app/app-routing.module.ts | 2 +- .../two-factor-auth.component.spec.ts | 9 +- .../two-factor-auth.component.ts | 8 +- libs/common/src/platform/misc/utils.ts | 2 +- 11 files changed, 153 insertions(+), 68 deletions(-) delete mode 100644 apps/web/src/app/auth/guards/deep-link.guard.ts rename apps/web/src/app/auth/guards/{ => deep-link}/deep-link.guard.spec.ts (92%) create mode 100644 apps/web/src/app/auth/guards/deep-link/deep-link.guard.ts create mode 100644 apps/web/src/app/auth/guards/deep-link/readme.md diff --git a/apps/web/src/app/admin-console/organizations/organization-routing.module.ts b/apps/web/src/app/admin-console/organizations/organization-routing.module.ts index e5c68b73546..4d8971f74fd 100644 --- a/apps/web/src/app/admin-console/organizations/organization-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/organization-routing.module.ts @@ -14,7 +14,7 @@ import { } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { deepLinkGuard } from "../../auth/guards/deep-link.guard"; +import { deepLinkGuard } from "../../auth/guards/deep-link/deep-link.guard"; import { VaultModule } from "./collections/vault.module"; import { isEnterpriseOrgGuard } from "./guards/is-enterprise-org.guard"; diff --git a/apps/web/src/app/auth/guards/deep-link.guard.ts b/apps/web/src/app/auth/guards/deep-link.guard.ts deleted file mode 100644 index 387e7b17e88..00000000000 --- a/apps/web/src/app/auth/guards/deep-link.guard.ts +++ /dev/null @@ -1,58 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { inject } from "@angular/core"; -import { CanActivateFn, Router } from "@angular/router"; - -import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; - -import { RouterService } from "../../core/router.service"; - -/** - * Guard to persist and apply deep links to handle users who are not unlocked. - * @returns returns true. If user is not Unlocked will store URL to state for redirect once - * user is unlocked/Authenticated. - */ -export function deepLinkGuard(): CanActivateFn { - return async (route, routerState) => { - // Inject Services - const authService = inject(AuthService); - const router = inject(Router); - const routerService = inject(RouterService); - - // Fetch State - const currentUrl = routerState.url; - const transientPreviousUrl = routerService.getPreviousUrl(); - const authStatus = await authService.getAuthStatus(); - - // Evaluate State - /** before anything else, check if the user is already unlocked. */ - if (authStatus === AuthenticationStatus.Unlocked) { - const persistedPreLoginUrl = await routerService.getAndClearLoginRedirectUrl(); - if (!Utils.isNullOrEmpty(persistedPreLoginUrl)) { - return router.navigateByUrl(persistedPreLoginUrl); - } - return true; - } - /** - * At this point the user is either `locked` or `loggedOut`, it doesn't matter. - * We opt to persist the currentUrl over the transient previousUrl. This supports - * the case where a user is locked out of their vault and they deep link from - * the "lock" page. - * - * When the user is locked out of their vault the currentUrl contains "lock" so it will - * not be persisted, the previousUrl will be persisted instead. - */ - if (isValidUrl(currentUrl)) { - await routerService.persistLoginRedirectUrl(currentUrl); - } else if (isValidUrl(transientPreviousUrl)) { - await routerService.persistLoginRedirectUrl(transientPreviousUrl); - } - return true; - }; - - function isValidUrl(url: string | null | undefined): boolean { - return !Utils.isNullOrEmpty(url) && !url?.toLocaleLowerCase().includes("/lock"); - } -} diff --git a/apps/web/src/app/auth/guards/deep-link.guard.spec.ts b/apps/web/src/app/auth/guards/deep-link/deep-link.guard.spec.ts similarity index 92% rename from apps/web/src/app/auth/guards/deep-link.guard.spec.ts rename to apps/web/src/app/auth/guards/deep-link/deep-link.guard.spec.ts index 82ed004cf54..dba4dbd8357 100644 --- a/apps/web/src/app/auth/guards/deep-link.guard.spec.ts +++ b/apps/web/src/app/auth/guards/deep-link/deep-link.guard.spec.ts @@ -7,7 +7,7 @@ import { MockProxy, mock } from "jest-mock-extended"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { RouterService } from "../../core/router.service"; +import { RouterService } from "../../../core/router.service"; import { deepLinkGuard } from "./deep-link.guard"; @@ -99,6 +99,18 @@ describe("Deep Link Guard", () => { expect(routerService.persistLoginRedirectUrl).not.toHaveBeenCalled(); }); + it('should not persist routerService.previousUrl when routerService.previousUrl contains "login-initiated"', async () => { + // Arrange + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Locked); + routerService.getPreviousUrl.mockReturnValue("/login-initiated"); + + // Act + await routerHarness.navigateByUrl("/lock-route"); + + // Assert + expect(routerService.persistLoginRedirectUrl).not.toHaveBeenCalled(); + }); + // Story: User's vault times out and previousUrl is undefined it("should not persist routerService.previousUrl when routerService.previousUrl is undefined", async () => { // Arrange diff --git a/apps/web/src/app/auth/guards/deep-link/deep-link.guard.ts b/apps/web/src/app/auth/guards/deep-link/deep-link.guard.ts new file mode 100644 index 00000000000..003f0580969 --- /dev/null +++ b/apps/web/src/app/auth/guards/deep-link/deep-link.guard.ts @@ -0,0 +1,99 @@ +import { inject } from "@angular/core"; +import { CanActivateFn, Router } from "@angular/router"; + +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; + +import { RouterService } from "../../../core/router.service"; + +/** + * Guard to persist and apply deep links to handle users who are not unlocked. + * @returns returns true. If user is not Unlocked will store URL to state for redirect once + * user is unlocked/Authenticated. + */ +export function deepLinkGuard(): CanActivateFn { + return async (route, routerState) => { + // Inject Services + const authService = inject(AuthService); + const router = inject(Router); + const routerService = inject(RouterService); + + // Fetch State + const currentUrl = routerState.url; + const transientPreviousUrl = routerService.getPreviousUrl(); + const authStatus = await authService.getAuthStatus(); + + // Evaluate State + /** before anything else, check if the user is already unlocked. */ + if (authStatus === AuthenticationStatus.Unlocked) { + const persistedPreLoginUrl: string | undefined = + await routerService.getAndClearLoginRedirectUrl(); + if (persistedPreLoginUrl === undefined) { + // Url us undefined, so there is nothing to navigate to. + return true; + } + // Check if the url is empty or null + if (!Utils.isNullOrEmpty(persistedPreLoginUrl)) { + // const urlTree: string | UrlTree = persistedPreLoginUrl; + return router.navigateByUrl(persistedPreLoginUrl); + } + return true; + } + /** + * At this point the user is either `locked` or `loggedOut`, it doesn't matter. + * We opt to persist the currentUrl over the transient previousUrl. This supports + * the case where a user is locked out of their vault and they deep link from + * the "lock" page. + * + * When the user is locked out of their vault the currentUrl contains "lock" so it will + * not be persisted, the previousUrl will be persisted instead. + */ + if (isValidUrl(currentUrl)) { + await routerService.persistLoginRedirectUrl(currentUrl); + } else if (isValidUrl(transientPreviousUrl) && transientPreviousUrl !== undefined) { + await routerService.persistLoginRedirectUrl(transientPreviousUrl); + } + return true; + }; + + /** + * Check if the URL is valid for deep linking. A valid url is described as not including + * "lock" or "login-initiated". Valid urls are only urls that are not part of login or + * decryption flows. + * We ignore the "lock" url because standard SSO flows will send users to the lock component. + * We ignore "login-initiated" because TDE users decrypting with master passwords are + * sent to the lock component. + * @param url The URL to check. + * @returns True if the URL is valid, false otherwise. + */ + function isValidUrl(url: string | null | undefined): boolean { + if (url === undefined || url === null) { + return false; + } + + if (Utils.isNullOrEmpty(url)) { + return false; + } + const lowerCaseUrl: string = url.toLocaleLowerCase(); + + /** + * "Login-initiated" ignored because it is used for TDE users decrypting from a new device. A TDE user + * can opt to decrypt using their password. Decrypting with a password will send the user to the lock component, + * which is protected by the deep link guard. We don't persist the `login-initiated` url because it is not a + * valid deep-link. We don't want users to be sent to the login-initiated url when they are unlocked. + * If we did navigate to the login-initiated url, the user would get caught by the TDE Guard and be sent + * to the vault and not the intended deep link. + * + * "Lock" is ignored because users cannot deep-link to the lock component if they are already unlocked. + * Users logging in with SSO will be sent to the lock component after they are authenticated with their IdP. + * SSO users would be navigated to the "lock" component loop if we persisted the "lock" url. + */ + + if (lowerCaseUrl.includes("/login-initiated") || lowerCaseUrl.includes("/lock")) { + return false; + } + + return true; + } +} diff --git a/apps/web/src/app/auth/guards/deep-link/readme.md b/apps/web/src/app/auth/guards/deep-link/readme.md new file mode 100644 index 00000000000..82aebf95476 --- /dev/null +++ b/apps/web/src/app/auth/guards/deep-link/readme.md @@ -0,0 +1,23 @@ +# Deep-link Guard + +The `deep-link.guard.ts` supports users who are trying to access a protected route from an unauthenticated or locked state. + +This guard will persist the protected URL to session state when a user is either unauthenticated or in an encrypted/locked state. This allows users to have multiple tabs of the application running simultaneously without interfering with 'previousUrl` functionality. + +Writing to session state allows users who are authenticating through SSO to be routed to their identity provider and back without losing the protected route they were trying to access in the first place. + +The deep link guard will not persist Urls that are in the middle of authentication or decryption. SSO users will sometimes have to decrypt their vault after a successful authentication. This is why we do not persist the `/lock` route. + +## General operation + +The `deep-link.guard.ts` will always return true. The `deep-link.guard.ts` will only persist a URL if the user is in an unauthenticated or locked state. The URL cannot contain `/lock` or `/login-initiated`. The persisted URL is cleared from state when it is read. + +## Routes to protect + +The deep link guards should be used on routes where a user will be navigated to a protected route but may not be authenticated, decrypted, or have an account. + +A use cases is the `emergency-access` route which is a link that is sent to the user's email address, and in order for them to accept the request, they must first authenticate and decrypt. + +## TDE Users decrypting/unlocking with password + +For TDE users opting to decrypt with a password they will be routed from the `login-initiated` to the `lock` route. We ignore the `login-initiated` route for this reason allowing TDE users who decrypt/unlock with a password to still be navigated to the initial request. diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 37da9a35f69..0d6ffb88ad6 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -48,7 +48,7 @@ import { VerifyRecoverDeleteOrgComponent } from "./admin-console/organizations/m import { AcceptFamilySponsorshipComponent } from "./admin-console/organizations/sponsorships/accept-family-sponsorship.component"; import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizations/sponsorships/families-for-enterprise-setup.component"; import { CreateOrganizationComponent } from "./admin-console/settings/create-organization.component"; -import { deepLinkGuard } from "./auth/guards/deep-link.guard"; +import { deepLinkGuard } from "./auth/guards/deep-link/deep-link.guard"; import { LoginViaWebAuthnComponent } from "./auth/login/login-via-webauthn/login-via-webauthn.component"; import { AcceptOrganizationComponent } from "./auth/organization-invite/accept-organization.component"; import { RecoverDeleteComponent } from "./auth/recover-delete.component"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts index a995b0cb1f4..f63140a8b23 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts @@ -6,7 +6,7 @@ import { canAccessSettingsTab } from "@bitwarden/common/admin-console/abstractio import { isEnterpriseOrgGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/is-enterprise-org.guard"; import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/org-permissions.guard"; import { OrganizationLayoutComponent } from "@bitwarden/web-vault/app/admin-console/organizations/layouts/organization-layout.component"; -import { deepLinkGuard } from "@bitwarden/web-vault/app/auth/guards/deep-link.guard"; +import { deepLinkGuard } from "@bitwarden/web-vault/app/auth/guards/deep-link/deep-link.guard"; import { SsoComponent } from "../../auth/sso/sso.component"; diff --git a/bitwarden_license/bit-web/src/app/app-routing.module.ts b/bitwarden_license/bit-web/src/app/app-routing.module.ts index 6aed12511c1..3f2803695eb 100644 --- a/bitwarden_license/bit-web/src/app/app-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/app-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule, Routes } from "@angular/router"; import { unauthGuardFn } from "@bitwarden/angular/auth/guards"; import { AnonLayoutWrapperComponent } from "@bitwarden/auth/angular"; -import { deepLinkGuard } from "@bitwarden/web-vault/app/auth/guards/deep-link.guard"; +import { deepLinkGuard } from "@bitwarden/web-vault/app/auth/guards/deep-link/deep-link.guard"; import { RouteDataProperties } from "@bitwarden/web-vault/app/core"; import { ProvidersModule } from "./admin-console/providers/providers.module"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts index de3fa7a3321..e52f08941d4 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts @@ -16,8 +16,10 @@ import { } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; @@ -72,6 +74,7 @@ describe("TwoFactorAuthComponent", () => { let mockEnvService: MockProxy; let mockLoginSuccessHandlerService: MockProxy; let mockTwoFactorAuthCompCacheService: MockProxy; + let mockAuthService: MockProxy; let mockUserDecryptionOpts: { noMasterPassword: UserDecryptionOptions; @@ -106,6 +109,7 @@ describe("TwoFactorAuthComponent", () => { mockDialogService = mock(); mockToastService = mock(); mockTwoFactorAuthCompService = mock(); + mockAuthService = mock(); mockEnvService = mock(); mockLoginSuccessHandlerService = mock(); @@ -204,6 +208,7 @@ describe("TwoFactorAuthComponent", () => { provide: TwoFactorAuthComponentCacheService, useValue: mockTwoFactorAuthCompCacheService, }, + { provide: AuthService, useValue: mockAuthService }, ], }); @@ -295,6 +300,7 @@ describe("TwoFactorAuthComponent", () => { it("navigates to the component's defined success route (vault is default) when the login is successful", async () => { mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); + mockAuthService.activeAccountStatus$ = new BehaviorSubject(AuthenticationStatus.Unlocked); // Act await component.submit("testToken"); @@ -316,13 +322,14 @@ describe("TwoFactorAuthComponent", () => { async (authType, expectedRoute) => { mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); currentAuthTypeSubject.next(authType); + mockAuthService.activeAccountStatus$ = new BehaviorSubject(AuthenticationStatus.Locked); // Act await component.submit("testToken"); // Assert expect(mockRouter.navigate).toHaveBeenCalledTimes(1); - expect(mockRouter.navigate).toHaveBeenCalledWith(["lock"], { + expect(mockRouter.navigate).toHaveBeenCalledWith([expectedRoute], { queryParams: { identifier: component.orgSsoIdentifier, }, diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 91901fa3544..b14a368e066 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -24,9 +24,10 @@ import { LoginSuccessHandlerService, } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; @@ -167,6 +168,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { private environmentService: EnvironmentService, private loginSuccessHandlerService: LoginSuccessHandlerService, private twoFactorAuthComponentCacheService: TwoFactorAuthComponentCacheService, + private authService: AuthService, ) {} async ngOnInit() { @@ -507,8 +509,8 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { } private async determineDefaultSuccessRoute(): Promise { - const authType = await firstValueFrom(this.loginStrategyService.currentAuthType$); - if (authType == AuthenticationType.Sso || authType == AuthenticationType.UserApiKey) { + const activeAccountStatus = await firstValueFrom(this.authService.activeAccountStatus$); + if (activeAccountStatus === AuthenticationStatus.Locked) { return "lock"; } diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index 203a04851c5..f9c5ab4a843 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -391,7 +391,7 @@ export class Utils { return str == null || typeof str !== "string" || str.trim() === ""; } - static isNullOrEmpty(str: string): boolean { + static isNullOrEmpty(str: string | null): boolean { return str == null || typeof str !== "string" || str == ""; } From 1c4d851046eca62a39e4c82c58f898eebd0b0946 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Wed, 21 May 2025 08:00:49 -0500 Subject: [PATCH 127/245] [PM-21005] Clear Add/Edit form cache when browser loses focus (#14634) --- .../view-cache/popup-view-cache.service.ts | 2 + .../popup-view-cache-background.service.ts | 40 ++++++++++++++++++- .../add-edit/add-edit-v2.component.ts | 22 ++++++++-- .../new-item-dropdown-v2.component.spec.ts | 3 +- .../new-item-dropdown-v2.component.ts | 14 +++---- .../platform/view-cache/view-cache.service.ts | 6 +++ .../default-cipher-form-cache.service.ts | 1 + 7 files changed, 73 insertions(+), 15 deletions(-) diff --git a/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts b/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts index 2a946982990..83d6edbc141 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts @@ -81,6 +81,7 @@ export class PopupViewCacheService implements ViewCacheService { injector = inject(Injector), initialValue, persistNavigation, + clearOnTabChange, } = options; const cachedValue = this.cache[key]?.value ? deserializer(JSON.parse(this.cache[key].value)) @@ -89,6 +90,7 @@ export class PopupViewCacheService implements ViewCacheService { const viewCacheOptions = { ...(persistNavigation && { persistNavigation }), + ...(clearOnTabChange && { clearOnTabChange }), }; effect( diff --git a/apps/browser/src/platform/services/popup-view-cache-background.service.ts b/apps/browser/src/platform/services/popup-view-cache-background.service.ts index 79c04e90aad..49eae15fbbd 100644 --- a/apps/browser/src/platform/services/popup-view-cache-background.service.ts +++ b/apps/browser/src/platform/services/popup-view-cache-background.service.ts @@ -1,4 +1,4 @@ -import { switchMap, delay, filter, concatMap } from "rxjs"; +import { switchMap, delay, filter, concatMap, map, first, of } from "rxjs"; import { CommandDefinition, MessageListener } from "@bitwarden/common/platform/messaging"; import { @@ -12,6 +12,7 @@ import { GlobalStateProvider, } from "@bitwarden/common/platform/state"; +import { BrowserApi } from "../browser/browser-api"; import { fromChromeEvent } from "../browser/from-chrome-event"; const popupClosedPortName = "new_popup"; @@ -21,6 +22,12 @@ export type ViewCacheOptions = { * Optional flag to persist the cached value between navigation events. */ persistNavigation?: boolean; + + /** + * When set, the cached value will be cleared when the user changes tabs. + * @optional + */ + clearOnTabChange?: true; }; export type ViewCacheState = { @@ -129,6 +136,37 @@ export class PopupViewCacheBackgroundService { ), ) .subscribe(); + + // On tab changed, excluding extension tabs + fromChromeEvent(chrome.tabs.onActivated) + .pipe( + switchMap((tabs) => BrowserApi.getTab(tabs[0].tabId)!), + switchMap((tab) => { + // FireFox sets the `url` to "about:blank" and won't populate the `url` until the `onUpdated` event + if (tab.url !== "about:blank") { + return of(tab); + } + + return fromChromeEvent(chrome.tabs.onUpdated).pipe( + first(), + switchMap(([tabId]) => BrowserApi.getTab(tabId)!), + ); + }), + map((tab) => tab.url || tab.pendingUrl), + filter((url) => !url?.startsWith(chrome.runtime.getURL(""))), + switchMap(() => + this.popupViewCacheState.update((state) => { + if (!state) { + return null; + } + // Only remove keys that are marked with `clearOnTabChange` + return Object.fromEntries( + Object.entries(state).filter(([, { options }]) => !options?.clearOnTabChange), + ); + }), + ), + ) + .subscribe(); } async clearState() { diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts index 30fd57a6bc6..e47f4637199 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts @@ -14,6 +14,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -40,6 +41,7 @@ import { } from "@bitwarden/vault"; import { BrowserFido2UserInterfaceSession } from "../../../../../autofill/fido2/services/browser-fido2-user-interface.service"; +import { BrowserApi } from "../../../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component"; import { PopupFooterComponent } from "../../../../../platform/popup/layout/popup-footer.component"; @@ -70,6 +72,7 @@ class QueryParams { this.uri = params.uri; this.username = params.username; this.name = params.name; + this.prefillNameAndURIFromTab = params.prefillNameAndURIFromTab; } /** @@ -116,6 +119,12 @@ class QueryParams { * Optional name to pre-fill for the cipher. */ name?: string; + + /** + * Optional flag to pre-fill the name and URI from the current tab. + * NOTE: This will override the `uri` and `name` query parameters if set to true. + */ + prefillNameAndURIFromTab?: true; } export type AddEditQueryParams = Partial>; @@ -281,8 +290,7 @@ export class AddEditV2Component implements OnInit { if (config.mode === "edit" && !config.originalCipher.edit) { config.mode = "partial-edit"; } - - config.initialValues = this.setInitialValuesFromParams(params); + config.initialValues = await this.setInitialValuesFromParams(params); const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(getUserId), @@ -326,7 +334,7 @@ export class AddEditV2Component implements OnInit { }); } - setInitialValuesFromParams(params: QueryParams) { + async setInitialValuesFromParams(params: QueryParams) { const initialValues = {} as OptionalInitialValues; if (params.folderId) { initialValues.folderId = params.folderId; @@ -346,6 +354,14 @@ export class AddEditV2Component implements OnInit { if (params.name) { initialValues.name = params.name; } + + if (params.prefillNameAndURIFromTab) { + const tab = await BrowserApi.getTabFromCurrentWindow(); + + initialValues.loginUri = tab.url; + initialValues.name = Utils.getHostname(tab.url); + } + return initialValues; } diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts index a9b92274c9e..54c6ba2f788 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts @@ -94,8 +94,7 @@ describe("NewItemDropdownV2Component", () => { collectionId: "777-888-999", organizationId: "444-555-666", folderId: "222-333-444", - uri: "https://example.com", - name: "example.com", + prefillNameAndURIFromTab: "true", }); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts index bb452b89c7b..1bcc4297b71 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts @@ -2,10 +2,9 @@ // @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; -import { Router, RouterLink } from "@angular/router"; +import { RouterLink } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums"; import { ButtonModule, DialogService, MenuModule, NoItemsModule } from "@bitwarden/components"; @@ -35,10 +34,8 @@ export class NewItemDropdownV2Component implements OnInit { */ @Input() initialValues: NewItemInitialValues; - constructor( - private router: Router, - private dialogService: DialogService, - ) {} + + constructor(private dialogService: DialogService) {} async ngOnInit() { this.tab = await BrowserApi.getTabFromCurrentWindow(); @@ -47,13 +44,12 @@ export class NewItemDropdownV2Component implements OnInit { buildQueryParams(type: CipherType): AddEditQueryParams { const poppedOut = BrowserPopupUtils.inPopout(window); - const loginDetails: { uri?: string; name?: string } = {}; + const loginDetails: { prefillNameAndURIFromTab?: string } = {}; // When a Login Cipher is created and the extension is not popped out, // pass along the uri and name if (!poppedOut && type === CipherType.Login && this.tab) { - loginDetails.uri = this.tab.url; - loginDetails.name = Utils.getHostname(this.tab.url); + loginDetails.prefillNameAndURIFromTab = "true"; } return { diff --git a/libs/angular/src/platform/view-cache/view-cache.service.ts b/libs/angular/src/platform/view-cache/view-cache.service.ts index 386ddc7bdd1..ed74ac0ba57 100644 --- a/libs/angular/src/platform/view-cache/view-cache.service.ts +++ b/libs/angular/src/platform/view-cache/view-cache.service.ts @@ -23,6 +23,12 @@ type BaseCacheOptions = { * Optional flag to persist the cached value between navigation events. */ persistNavigation?: boolean; + + /** + * When set, the cached value will be cleared when the user changes tabs. + * @optional + */ + clearOnTabChange?: true; } & (T extends JsonValue ? Deserializer : Required>); export type SignalCacheOptions = BaseCacheOptions & { diff --git a/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.ts index b4a8138e025..521cc09f140 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.ts @@ -27,6 +27,7 @@ export class CipherFormCacheService { key: CIPHER_FORM_CACHE_KEY, initialValue: null, deserializer: CipherView.fromJSON, + clearOnTabChange: true, }); constructor() { From cf7da2ebdcf4535d7be90cefa47208442e814c8a Mon Sep 17 00:00:00 2001 From: Conner Turnbull <133619638+cturnbull-bitwarden@users.noreply.github.com> Date: Wed, 21 May 2025 11:28:22 -0400 Subject: [PATCH 128/245] Added data validation where it was missing to upgrade dialog (#14866) --- .../change-plan-dialog.component.ts | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts index 49c5bb775b1..9b6694a3bbe 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts @@ -627,6 +627,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } get passwordManagerSubtotal() { + if (!this.selectedPlan || !this.selectedPlan.PasswordManager) { + return 0; + } + let subTotal = this.selectedPlan.PasswordManager.basePrice; if (this.selectedPlan.PasswordManager.hasAdditionalSeatsOption) { subTotal += this.passwordManagerSeatTotal(this.selectedPlan); @@ -638,10 +642,12 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } secretsManagerSubtotal() { - this.secretsManagerTotal = 0; - const plan = this.selectedSecretsManagerPlan; + const plan = this.selectedPlan; + if (!plan || !plan.SecretsManager) { + return this.secretsManagerTotal || 0; + } - if (!this.organization.useSecretsManager) { + if (this.secretsManagerTotal) { return this.secretsManagerTotal; } @@ -653,6 +659,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } get passwordManagerSeats() { + if (!this.selectedPlan) { + return 0; + } + if (this.selectedPlan.productTier === ProductTierType.Families) { return this.selectedPlan.PasswordManager.baseSeats; } @@ -660,7 +670,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } get total() { - if (this.organization && this.organization.useSecretsManager) { + if (!this.organization || !this.selectedPlan) { + return 0; + } + + if (this.organization.useSecretsManager) { return ( this.passwordManagerSubtotal + this.additionalStorageTotal(this.selectedPlan) + @@ -680,6 +694,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } get additionalServiceAccount() { + if (!this.currentPlan || !this.currentPlan.SecretsManager) { + return 0; + } + const baseServiceAccount = this.currentPlan.SecretsManager?.baseServiceAccount || 0; const usedServiceAccounts = this.sub?.smServiceAccounts || 0; @@ -1096,8 +1114,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { get submitButtonLabel(): string { if ( + this.organization && + this.sub && this.organization.productTierType !== ProductTierType.Free && - this.sub.subscription.status === "canceled" + this.sub.subscription?.status === "canceled" ) { return this.i18nService.t("restart"); } else { From fd10a26df9903e5418dc782dd2981c5bfdf59ad5 Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Wed, 21 May 2025 13:46:02 -0400 Subject: [PATCH 129/245] [PM-18804] generator nudges (#14705) * added generator spotlight to credential generator component * moved generator spotlight to browser component and add as slot in libs. update copy for send * added an aria label for the generator nudge body content * new copy and styles for browser send will be behind feature flag * update featureflag call to observable in send-v2 * changed how nudge text is made in credential generator * added new observable to vault nudges to return specific boolean. Update naming of vault types. update observable calls in credential-generator and send-v2 * update send-v2 and credential generator to use new renamed nudges * update to create nudge generator spotlight component. using this inside the credential generator for nudge spotlight * fix imports for Nudge related code * add libs/angular to storybook --------- Co-authored-by: Nick Krantz --- .storybook/main.ts | 1 + apps/browser/src/_locales/en/messages.json | 25 ++++++++++++ .../popup/settings/autofill.component.ts | 2 +- .../popup/send-v2/send-v2.component.html | 32 +++++++++++----- .../popup/send-v2/send-v2.component.spec.ts | 2 + .../tools/popup/send-v2/send-v2.component.ts | 24 +++++++----- .../components/vault-v2/vault-v2.component.ts | 5 +-- .../spotlight/spotlight.component.html | 0 .../spotlight/spotlight.component.ts | 0 .../components/spotlight/spotlight.stories.ts | 0 .../src/vault/services/nudges.service.ts | 2 + .../src/credential-generator.component.html | 2 + .../components/src/generator.module.ts | 2 + .../nudge-generator-spotlight.component.html | 15 ++++++++ .../nudge-generator-spotlight.component.ts | 38 +++++++++++++++++++ libs/tools/generator/components/tsconfig.json | 3 +- .../new-send-dropdown.component.html | 8 +++- .../new-send-dropdown.component.ts | 3 +- .../new-item-nudge.component.ts | 3 +- libs/vault/src/index.ts | 2 - 20 files changed, 139 insertions(+), 30 deletions(-) rename libs/{vault/src => angular/src/vault}/components/spotlight/spotlight.component.html (100%) rename libs/{vault/src => angular/src/vault}/components/spotlight/spotlight.component.ts (100%) rename libs/{vault/src => angular/src/vault}/components/spotlight/spotlight.stories.ts (100%) create mode 100644 libs/tools/generator/components/src/nudge-generator-spotlight.component.html create mode 100644 libs/tools/generator/components/src/nudge-generator-spotlight.component.ts diff --git a/.storybook/main.ts b/.storybook/main.ts index 9583d1fc6f2..d5d116e99be 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -22,6 +22,7 @@ const config: StorybookConfig = { "../bitwarden_license/bit-web/src/**/*.stories.@(js|jsx|ts|tsx)", "../libs/tools/card/src/**/*.mdx", "../libs/tools/card/src/**/*.stories.@(js|jsx|ts|tsx)", + "../libs/angular/src/**/*.stories.@(js|jsx|ts|tsx)", ], addons: [ getAbsolutePath("@storybook/addon-links"), diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 99ca31bafd5..4775d1f7af0 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5350,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.ts b/apps/browser/src/autofill/popup/settings/autofill.component.ts index 0605f5a03a6..e1a5a2fc218 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill.component.ts @@ -15,6 +15,7 @@ import { filter, firstValueFrom, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; +import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { @@ -56,7 +57,6 @@ import { SelectModule, TypographyModule, } from "@bitwarden/components"; -import { SpotlightComponent } from "@bitwarden/vault"; import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.html b/apps/browser/src/tools/popup/send-v2/send-v2.component.html index d51bda45b55..d271f67fa3b 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.html +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.html @@ -20,15 +20,29 @@ *ngIf="listState === sendState.Empty" class="tw-flex tw-flex-col tw-h-full tw-justify-center" > - - {{ "sendsNoItemsTitle" | i18n }} - {{ "sendsNoItemsMessage" | i18n }} - - + + + {{ "sendsNoItemsTitle" | i18n }} + {{ "sendsNoItemsMessage" | i18n }} + + + + + + {{ "sendsTitleNoItems" | i18n }} + {{ "sendsBodyNoItems" | i18n }} + + +
diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts index 6fc4793f5c0..c1f8e9fb263 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts @@ -6,6 +6,7 @@ import { MockProxy, mock } from "jest-mock-extended"; import { of, BehaviorSubject } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { NudgesService } from "@bitwarden/angular/vault"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -121,6 +122,7 @@ describe("SendV2Component", () => { { provide: SendListFiltersService, useValue: sendListFiltersService }, { provide: PopupRouterCacheService, useValue: mock() }, { provide: PolicyService, useValue: policyService }, + { provide: NudgesService, useValue: mock() }, ], }).compileComponents(); diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts index def425a51a5..9fc19e98b34 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts @@ -1,10 +1,10 @@ import { CommonModule } from "@angular/common"; -import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Component, OnDestroy } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { RouterLink } from "@angular/router"; -import { combineLatest, switchMap } from "rxjs"; +import { combineLatest, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -12,13 +12,13 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { ButtonModule, CalloutModule, Icons, NoItemsModule } from "@bitwarden/components"; import { - NoSendsIcon, NewSendDropdownComponent, - SendListItemsContainerComponent, + NoSendsIcon, SendItemsService, - SendSearchComponent, SendListFiltersComponent, SendListFiltersService, + SendListItemsContainerComponent, + SendSearchComponent, } from "@bitwarden/send-ui"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; @@ -46,14 +46,13 @@ export enum SendState { JslibModule, CommonModule, ButtonModule, - RouterLink, NewSendDropdownComponent, SendListItemsContainerComponent, SendListFiltersComponent, SendSearchComponent, ], }) -export class SendV2Component implements OnInit, OnDestroy { +export class SendV2Component implements OnDestroy { sendType = SendType; sendState = SendState; @@ -63,6 +62,12 @@ export class SendV2Component implements OnInit, OnDestroy { protected title: string = "allSends"; protected noItemIcon = NoSendsIcon; protected noResultsIcon = Icons.NoResults; + private activeUserId$ = this.accountService.activeAccount$.pipe(getUserId); + protected showSendSpotlight$: Observable = this.activeUserId$.pipe( + switchMap((userId) => + this.nudgesService.showNudgeSpotlight$(NudgeType.SendNudgeStatus, userId), + ), + ); protected sendsDisabled = false; @@ -71,6 +76,7 @@ export class SendV2Component implements OnInit, OnDestroy { protected sendListFiltersService: SendListFiltersService, private policyService: PolicyService, private accountService: AccountService, + private nudgesService: NudgesService, ) { combineLatest([ this.sendItemsService.emptyList$, @@ -111,7 +117,5 @@ export class SendV2Component implements OnInit, OnDestroy { }); } - ngOnInit(): void {} - ngOnDestroy(): void {} } diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 474524cf883..db853d45940 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -17,10 +17,10 @@ import { import { JslibModule } from "@bitwarden/angular/jslib.module"; import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; +import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -31,7 +31,7 @@ import { NoItemsModule, TypographyModule, } from "@bitwarden/components"; -import { DecryptionFailureDialogComponent, SpotlightComponent, VaultIcons } from "@bitwarden/vault"; +import { DecryptionFailureDialogComponent, VaultIcons } from "@bitwarden/vault"; import { CurrentAccountComponent } from "../../../../auth/popup/account-switching/current-account.component"; import { BrowserApi } from "../../../../platform/browser/browser-api"; @@ -154,7 +154,6 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { private introCarouselService: IntroCarouselService, private nudgesService: NudgesService, private router: Router, - private i18nService: I18nService, ) { combineLatest([ this.vaultPopupItemsService.emptyVault$, diff --git a/libs/vault/src/components/spotlight/spotlight.component.html b/libs/angular/src/vault/components/spotlight/spotlight.component.html similarity index 100% rename from libs/vault/src/components/spotlight/spotlight.component.html rename to libs/angular/src/vault/components/spotlight/spotlight.component.html diff --git a/libs/vault/src/components/spotlight/spotlight.component.ts b/libs/angular/src/vault/components/spotlight/spotlight.component.ts similarity index 100% rename from libs/vault/src/components/spotlight/spotlight.component.ts rename to libs/angular/src/vault/components/spotlight/spotlight.component.ts diff --git a/libs/vault/src/components/spotlight/spotlight.stories.ts b/libs/angular/src/vault/components/spotlight/spotlight.stories.ts similarity index 100% rename from libs/vault/src/components/spotlight/spotlight.stories.ts rename to libs/angular/src/vault/components/spotlight/spotlight.stories.ts diff --git a/libs/angular/src/vault/services/nudges.service.ts b/libs/angular/src/vault/services/nudges.service.ts index 55e6009a8e0..006f568f33e 100644 --- a/libs/angular/src/vault/services/nudges.service.ts +++ b/libs/angular/src/vault/services/nudges.service.ts @@ -40,6 +40,8 @@ export enum NudgeType { NewIdentityItemStatus = "new-identity-item-status", NewNoteItemStatus = "new-note-item-status", NewSshItemStatus = "new-ssh-item-status", + GeneratorNudgeStatus = "generator-nudge-status", + SendNudgeStatus = "send-nudge-status", } export const NUDGE_DISMISSED_DISK_KEY = new UserKeyDefinition< diff --git a/libs/tools/generator/components/src/credential-generator.component.html b/libs/tools/generator/components/src/credential-generator.component.html index e95df388f39..24146968456 100644 --- a/libs/tools/generator/components/src/credential-generator.component.html +++ b/libs/tools/generator/components/src/credential-generator.component.html @@ -11,6 +11,8 @@ + +
diff --git a/libs/tools/generator/components/src/generator.module.ts b/libs/tools/generator/components/src/generator.module.ts index f0d09b53ebb..d710f368106 100644 --- a/libs/tools/generator/components/src/generator.module.ts +++ b/libs/tools/generator/components/src/generator.module.ts @@ -22,6 +22,7 @@ import { CatchallSettingsComponent } from "./catchall-settings.component"; import { CredentialGeneratorComponent } from "./credential-generator.component"; import { ForwarderSettingsComponent } from "./forwarder-settings.component"; import { GeneratorServicesModule } from "./generator-services.module"; +import { NudgeGeneratorSpotlightComponent } from "./nudge-generator-spotlight.component"; import { PassphraseSettingsComponent } from "./passphrase-settings.component"; import { PasswordGeneratorComponent } from "./password-generator.component"; import { PasswordSettingsComponent } from "./password-settings.component"; @@ -48,6 +49,7 @@ import { UsernameSettingsComponent } from "./username-settings.component"; SelectModule, ToggleGroupModule, TypographyModule, + NudgeGeneratorSpotlightComponent, ], declarations: [ CatchallSettingsComponent, diff --git a/libs/tools/generator/components/src/nudge-generator-spotlight.component.html b/libs/tools/generator/components/src/nudge-generator-spotlight.component.html new file mode 100644 index 00000000000..9c65a1cea96 --- /dev/null +++ b/libs/tools/generator/components/src/nudge-generator-spotlight.component.html @@ -0,0 +1,15 @@ +
+ +

+ {{ "generatorNudgeBodyOne" | i18n }} + {{ "generatorNudgeBodyTwo" | i18n }} +

+
+
diff --git a/libs/tools/generator/components/src/nudge-generator-spotlight.component.ts b/libs/tools/generator/components/src/nudge-generator-spotlight.component.ts new file mode 100644 index 00000000000..a0008bac782 --- /dev/null +++ b/libs/tools/generator/components/src/nudge-generator-spotlight.component.ts @@ -0,0 +1,38 @@ +import { AsyncPipe, CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { firstValueFrom, Observable, switchMap } from "rxjs"; + +import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; +import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { TypographyModule } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; + +@Component({ + standalone: true, + selector: "nudge-generator-spotlight", + templateUrl: "nudge-generator-spotlight.component.html", + imports: [I18nPipe, SpotlightComponent, AsyncPipe, CommonModule, TypographyModule], +}) +export class NudgeGeneratorSpotlightComponent { + protected readonly NudgeType = NudgeType; + private activeUserId$ = this.accountService.activeAccount$.pipe(getUserId); + protected showGeneratorSpotlight$: Observable = this.activeUserId$.pipe( + switchMap((userId) => + this.nudgesService.showNudgeSpotlight$(NudgeType.GeneratorNudgeStatus, userId), + ), + ); + + constructor( + private nudgesService: NudgesService, + private accountService: AccountService, + ) {} + + async dismissGeneratorSpotlight(type: NudgeType) { + const activeUserId = await firstValueFrom(this.activeUserId$); + + await this.nudgesService.dismissNudge(type, activeUserId as UserId); + } +} diff --git a/libs/tools/generator/components/tsconfig.json b/libs/tools/generator/components/tsconfig.json index e0e4da268da..9a3a08b40fc 100644 --- a/libs/tools/generator/components/tsconfig.json +++ b/libs/tools/generator/components/tsconfig.json @@ -11,7 +11,8 @@ "@bitwarden/generator-history": ["../../../tools/generator/extensions/history/src"], "@bitwarden/key-management": ["../../../key-management/src"], "@bitwarden/platform": ["../../../platform/src"], - "@bitwarden/ui-common": ["../../../ui/common/src"] + "@bitwarden/ui-common": ["../../../ui/common/src"], + "@bitwarden/vault": ["../../../vault/src"] } }, "include": ["src"], diff --git a/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.html b/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.html index ef8b28aba33..c62e646540b 100644 --- a/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.html +++ b/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.html @@ -1,4 +1,10 @@ - diff --git a/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.ts b/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.ts index 19f9d3a174a..6563789f1c7 100644 --- a/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.ts +++ b/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.ts @@ -7,7 +7,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; -import { BadgeModule, ButtonModule, MenuModule } from "@bitwarden/components"; +import { BadgeModule, ButtonModule, ButtonType, MenuModule } from "@bitwarden/components"; @Component({ selector: "tools-new-send-dropdown", @@ -17,6 +17,7 @@ import { BadgeModule, ButtonModule, MenuModule } from "@bitwarden/components"; }) export class NewSendDropdownComponent implements OnInit { @Input() hideIcon: boolean = false; + @Input() buttonType: ButtonType = "primary"; sendType = SendType; diff --git a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts index 6781cafcbb2..705b98f241a 100644 --- a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts +++ b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts @@ -3,14 +3,13 @@ import { Component, Input, OnInit } from "@angular/core"; import { firstValueFrom } from "rxjs"; import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; +import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/sdk-internal"; -import { SpotlightComponent } from "../../../components/spotlight/spotlight.component"; - @Component({ selector: "vault-new-item-nudge", templateUrl: "./new-item-nudge.component.html", diff --git a/libs/vault/src/index.ts b/libs/vault/src/index.ts index 9d19e4c239d..b39bb85ab30 100644 --- a/libs/vault/src/index.ts +++ b/libs/vault/src/index.ts @@ -27,5 +27,3 @@ export { SshImportPromptService } from "./services/ssh-import-prompt.service"; export * from "./abstractions/change-login-password.service"; export * from "./services/default-change-login-password.service"; - -export { SpotlightComponent } from "./components/spotlight/spotlight.component"; From 105ec701b9e7b1cc4d36169c5b615da5dc47b04f Mon Sep 17 00:00:00 2001 From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com> Date: Wed, 21 May 2025 13:03:18 -0500 Subject: [PATCH 130/245] chore(tailwind): [PM-20609] Migrate duo-redirect.html * Update duo-redirect to use Tailwind classes similar to sso.html * Update dynamic classes to tailwind * fix: updating styling * Update button and logo styles to match previous * Update button styles * Update button styles --------- Co-authored-by: Ike Kottlowski Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> --- apps/web/src/connectors/duo-redirect.html | 31 ++++++++++------------- apps/web/src/connectors/duo-redirect.ts | 9 ++++--- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/apps/web/src/connectors/duo-redirect.html b/apps/web/src/connectors/duo-redirect.html index bfbbe8d216e..bcd1c7fccd3 100644 --- a/apps/web/src/connectors/duo-redirect.html +++ b/apps/web/src/connectors/duo-redirect.html @@ -10,23 +10,20 @@ -
-
- Bitwarden -
-

- -

-
+
+ Bitwarden +
+

+ +

diff --git a/apps/web/src/connectors/duo-redirect.ts b/apps/web/src/connectors/duo-redirect.ts index 5389b31f6af..ae8f84715db 100644 --- a/apps/web/src/connectors/duo-redirect.ts +++ b/apps/web/src/connectors/duo-redirect.ts @@ -108,7 +108,7 @@ function displayHandoffMessage(client: string) { if (!content) { throw new Error("content element not found"); } - content.className = "text-center"; + content.className = "tw-text-center"; content.innerHTML = ""; const h1 = document.createElement("h1"); @@ -123,8 +123,8 @@ function displayHandoffMessage(client: string) { ? localeService.t("thisWindowWillCloseIn5Seconds") : localeService.t("youMayCloseThisWindow"); - h1.className = "font-weight-semibold"; - p.className = "mb-4"; + h1.className = "tw-font-semibold"; + p.className = "tw-mb-4"; content.appendChild(h1); content.appendChild(p); @@ -133,7 +133,8 @@ function displayHandoffMessage(client: string) { if (client == "web") { const button = document.createElement("button"); button.textContent = localeService.t("close"); - button.className = "bg-primary text-white border-0 rounded py-2 px-3"; + button.className = + "tw-bg-primary-600 hover:tw-bg-primary-700 tw-text-contrast tw-px-4 tw-py-2 tw-rounded-md tw-transition tw-border-transparent tw-text-center focus:tw-outline-none"; button.addEventListener("click", () => { window.close(); From 27884d9ffe4400314ca19387b62ef7e6a0703c42 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Wed, 21 May 2025 13:43:10 -0500 Subject: [PATCH 131/245] fix `SpotlightComponent` import (#14868) --- .../src/auth/popup/settings/account-security.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index b2380a1a47e..d7b8aa573d4 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -23,6 +23,7 @@ import { import { JslibModule } from "@bitwarden/angular/jslib.module"; import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; +import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component"; import { FingerprintDialogComponent, VaultTimeoutInputComponent } from "@bitwarden/auth/angular"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -65,7 +66,6 @@ import { BiometricStateService, BiometricsStatus, } from "@bitwarden/key-management"; -import { SpotlightComponent } from "@bitwarden/vault"; import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors"; import { BrowserApi } from "../../../platform/browser/browser-api"; From f0b255117e7b2c7bee1d70c8e200da48d38e4b67 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 May 2025 16:35:30 -0400 Subject: [PATCH 132/245] [deps] Autofill: Update lit to v3.3.0 (#14479) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 579ff539de3..2e2623f89cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,7 +50,7 @@ "koa": "2.16.1", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", - "lit": "3.2.1", + "lit": "3.3.0", "lowdb": "1.0.0", "lunr": "2.3.9", "multer": "1.4.5-lts.2", @@ -26466,14 +26466,14 @@ } }, "node_modules/lit": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.2.1.tgz", - "integrity": "sha512-1BBa1E/z0O9ye5fZprPtdqnc0BFzxIxTTOO/tQFmyC/hj1O3jL4TfmLBw0WEwjAokdLwpclkvGgDJwTIh0/22w==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.0.tgz", + "integrity": "sha512-DGVsqsOIHBww2DqnuZzW7QsuCdahp50ojuDaBPC7jUDRpYoH0z7kHBBYZewRzer75FwtrkmkKk7iOAwSaWdBmw==", "license": "BSD-3-Clause", "dependencies": { - "@lit/reactive-element": "^2.0.4", - "lit-element": "^4.1.0", - "lit-html": "^3.2.0" + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" } }, "node_modules/lit-element": { diff --git a/package.json b/package.json index b1e91d92771..ce6adf3009d 100644 --- a/package.json +++ b/package.json @@ -185,7 +185,7 @@ "koa": "2.16.1", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", - "lit": "3.2.1", + "lit": "3.3.0", "lowdb": "1.0.0", "lunr": "2.3.9", "multer": "1.4.5-lts.2", From 121ff93ea18282160453436f7039e4b258b34d55 Mon Sep 17 00:00:00 2001 From: Patrick-Pimentel-Bitwarden Date: Wed, 21 May 2025 16:57:32 -0400 Subject: [PATCH 133/245] fix(policy-enforcement): [PM-21085] Fix Bug with Policy Enforcement (#14870) - Fixed up code that was not properly using the policy api. (#14870) --- .../app/auth/core/services/login/web-login-component.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts index c644f26dd90..36e7143ccd0 100644 --- a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts +++ b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts @@ -98,7 +98,7 @@ export class WebLoginComponentService const enforcedPasswordPolicyOptions = await firstValueFrom( this.accountService.activeAccount$.pipe( getUserId, - switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)), + switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId, policies)), ), ); From 0555d827c635913ab679a4abb7881d458fa2dcae Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Wed, 21 May 2025 18:06:06 -0400 Subject: [PATCH 134/245] exclude fido2crednetials when creating login item from template via CLI (#14766) --- libs/common/src/models/export/login.export.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/common/src/models/export/login.export.ts b/libs/common/src/models/export/login.export.ts index d24c084aa48..dd0cfa7d32b 100644 --- a/libs/common/src/models/export/login.export.ts +++ b/libs/common/src/models/export/login.export.ts @@ -15,7 +15,7 @@ export class LoginExport { req.username = "jdoe"; req.password = "myp@ssword123"; req.totp = "JBSWY3DPEHPK3PXP"; - req.fido2Credentials = [Fido2CredentialExport.template()]; + req.fido2Credentials = []; return req; } @@ -48,7 +48,7 @@ export class LoginExport { username: string; password: string; totp: string; - fido2Credentials: Fido2CredentialExport[] = []; + fido2Credentials: Fido2CredentialExport[]; constructor(o?: LoginView | LoginDomain) { if (o == null) { From 068c63e891f2d6e396c86bef1f2668e77ac1485f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 22 May 2025 15:05:28 +0200 Subject: [PATCH 135/245] Fix send rotation broken due to incorrect types (#14874) --- libs/common/src/tools/send/services/send.service.spec.ts | 6 ++---- libs/common/src/tools/send/services/send.service.ts | 5 +++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/libs/common/src/tools/send/services/send.service.spec.ts b/libs/common/src/tools/send/services/send.service.spec.ts index 65fd53edd75..611cc9c7b76 100644 --- a/libs/common/src/tools/send/services/send.service.spec.ts +++ b/libs/common/src/tools/send/services/send.service.spec.ts @@ -477,11 +477,9 @@ describe("SendService", () => { let encryptedKey: EncString; beforeEach(() => { - encryptService.unwrapSymmetricKey.mockResolvedValue( - new SymmetricCryptoKey(new Uint8Array(32)), - ); + encryptService.decryptBytes.mockResolvedValue(new Uint8Array(16)); encryptedKey = new EncString("Re-encrypted Send Key"); - encryptService.wrapSymmetricKey.mockResolvedValue(encryptedKey); + encryptService.encryptBytes.mockResolvedValue(encryptedKey); }); it("returns re-encrypted user sends", async () => { diff --git a/libs/common/src/tools/send/services/send.service.ts b/libs/common/src/tools/send/services/send.service.ts index db3834789c8..3a5bcbe997b 100644 --- a/libs/common/src/tools/send/services/send.service.ts +++ b/libs/common/src/tools/send/services/send.service.ts @@ -292,8 +292,9 @@ export class SendService implements InternalSendServiceAbstraction { ) { const requests = await Promise.all( sends.map(async (send) => { - const sendKey = await this.encryptService.unwrapSymmetricKey(send.key, originalUserKey); - send.key = await this.encryptService.wrapSymmetricKey(sendKey, rotateUserKey); + // Send key is not a key but a 16 byte seed used to derive the key + const sendKey = await this.encryptService.decryptBytes(send.key, originalUserKey); + send.key = await this.encryptService.encryptBytes(sendKey, rotateUserKey); return new SendWithIdRequest(send); }), ); From 46a0b709fe5b17027b571affbcbc82eb58f61745 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Thu, 22 May 2025 09:15:30 -0400 Subject: [PATCH 136/245] [PM-21644] Cannot retrieve attachment from bw serve (#14806) * Modified saveAttachmenttofIle to implement callback attachment content decryption * renamed parameter --- apps/cli/src/commands/download.command.ts | 9 +++---- apps/cli/src/commands/get.command.ts | 26 ++++++++++++------- .../tools/send/commands/receive.command.ts | 9 ++++++- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/apps/cli/src/commands/download.command.ts b/apps/cli/src/commands/download.command.ts index 472b084f5d7..2c75617ca5b 100644 --- a/apps/cli/src/commands/download.command.ts +++ b/apps/cli/src/commands/download.command.ts @@ -2,8 +2,6 @@ // @ts-strict-ignore import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { Response } from "../models/response"; import { FileResponse } from "../models/response/file.response"; @@ -25,15 +23,15 @@ export abstract class DownloadCommand { /** * Fetches an attachment via the url, decrypts it's content and saves it to a file * @param url - url used to retrieve the attachment - * @param key - SymmetricCryptoKey to decrypt the file contents * @param fileName - filename used when written to disk + * @param decrypt - Function used to decrypt the response * @param output - If output is empty or `--raw` was passed to the initial command the content is output onto stdout * @returns Promise */ protected async saveAttachmentToFile( url: string, - key: SymmetricCryptoKey, fileName: string, + decrypt: (resp: globalThis.Response) => Promise, output?: string, ) { const response = await this.apiService.nativeFetch( @@ -46,8 +44,7 @@ export abstract class DownloadCommand { } try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const decBuf = await this.encryptService.decryptFileData(encBuf, key); + const decBuf = await decrypt(response); if (process.env.BW_SERVE === "true") { const res = new FileResponse(Buffer.from(decBuf), fileName); return Response.success(res); diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 60be0a8d2cb..8554f8e2ae1 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -27,7 +27,7 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; -import { OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; @@ -345,12 +345,11 @@ export class GetCommand extends DownloadCommand { return Response.multipleResults(attachments.map((a) => a.id)); } - const account = await firstValueFrom(this.accountService.activeAccount$); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const canAccessPremium = await firstValueFrom( - this.accountProfileService.hasPremiumFromAnySource$(account.id), + this.accountProfileService.hasPremiumFromAnySource$(activeUserId), ); if (!canAccessPremium) { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const originalCipher = await this.cipherService.get(cipher.id, activeUserId); if (originalCipher == null || originalCipher.organizationId == null) { return Response.error("Premium status is required to use this feature."); @@ -374,11 +373,20 @@ export class GetCommand extends DownloadCommand { } } - const key = - attachments[0].key != null - ? attachments[0].key - : await this.keyService.getOrgKey(cipher.organizationId); - return await this.saveAttachmentToFile(url, key, attachments[0].fileName, options.output); + const decryptBufferFn = (resp: globalThis.Response) => + this.cipherService.getDecryptedAttachmentBuffer( + cipher.id as CipherId, + attachments[0], + resp, + activeUserId, + ); + + return await this.saveAttachmentToFile( + url, + attachments[0].fileName, + decryptBufferFn, + options.output, + ); } private async getFolder(id: string) { diff --git a/apps/cli/src/tools/send/commands/receive.command.ts b/apps/cli/src/tools/send/commands/receive.command.ts index c67b4213d97..a412f7c1667 100644 --- a/apps/cli/src/tools/send/commands/receive.command.ts +++ b/apps/cli/src/tools/send/commands/receive.command.ts @@ -11,6 +11,7 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendAccess } from "@bitwarden/common/tools/send/models/domain/send-access"; @@ -98,10 +99,16 @@ export class SendReceiveCommand extends DownloadCommand { this.sendAccessRequest, apiUrl, ); + + const decryptBufferFn = async (resp: globalThis.Response) => { + const encBuf = await EncArrayBuffer.fromResponse(resp); + return this.encryptService.decryptFileData(encBuf, this.decKey); + }; + return await this.saveAttachmentToFile( downloadData.url, - this.decKey, response?.file?.fileName, + decryptBufferFn, options.output, ); } From 9417d8a94328ce636c55dcbe987ed3e7b1846938 Mon Sep 17 00:00:00 2001 From: Brandon Treston Date: Thu, 22 May 2025 09:35:39 -0400 Subject: [PATCH 137/245] [PM-18633] Remove feature flagged logic (#14856) * remove feature flagged logic * clean up --- .../manage/group-add-edit.component.html | 2 +- .../manage/group-add-edit.component.ts | 9 +++--- .../member-dialog.component.html | 4 +-- .../member-dialog/member-dialog.component.ts | 24 +++++--------- .../collection-dialog.component.html | 2 +- .../collection-dialog.component.ts | 31 +++---------------- libs/common/src/enums/feature-flag.enum.ts | 2 -- 7 files changed, 21 insertions(+), 53 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html index 5c8c0c07f88..101512dea04 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html @@ -23,7 +23,7 @@ {{ "characterMaximum" | i18n: 100 }} - + {{ "externalId" | i18n }} {{ "externalIdDesc" | i18n }} diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts index 53a6a3cf196..ca7d07220b2 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts @@ -28,7 +28,6 @@ import { } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -147,6 +146,10 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { return this.params.organizationId; } + protected get isExternalIdVisible(): boolean { + return !!this.groupForm.get("externalId")?.value; + } + protected get editMode(): boolean { return this.groupId != null; } @@ -227,10 +230,6 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { this.groupDetails$, ]).pipe(map(([allowAdminAccess, groupDetails]) => !allowAdminAccess && groupDetails != null)); - protected isExternalIdVisible$ = this.configService - .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) - .pipe(map((isEnabled) => !isEnabled || !!this.groupForm.get("externalId")?.value)); - constructor( @Inject(DIALOG_DATA) private params: GroupAddEditDialogParams, private dialogRef: DialogRef, diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html index 9564952f511..fa0a7bd85a3 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html @@ -177,13 +177,13 @@ - + {{ "externalId" | i18n }} {{ "externalIdDesc" | i18n }} - + {{ "ssoExternalId" | i18n }} {{ "ssoExternalIdDesc" | i18n }} diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index 10bbc5cfe52..9adfb1db3f2 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -157,28 +157,20 @@ export class MemberDialogComponent implements OnDestroy { manageResetPassword: false, }); - protected isExternalIdVisible$ = this.configService - .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) - .pipe( - map((isEnabled) => { - return !isEnabled || !!this.formGroup.get("externalId")?.value; - }), - ); + get isExternalIdVisible(): boolean { + return !!this.formGroup.get("externalId")?.value; + } - protected isSsoExternalIdVisible$ = this.configService - .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) - .pipe( - map((isEnabled) => { - return isEnabled && !!this.formGroup.get("ssoExternalId")?.value; - }), - ); - - private destroy$ = new Subject(); + get isSsoExternalIdVisible(): boolean { + return !!this.formGroup.get("ssoExternalId")?.value; + } get customUserTypeSelected(): boolean { return this.formGroup.value.type === OrganizationUserType.Custom; } + private destroy$ = new Subject(); + isEditDialogParams( params: EditMemberDialogParams | AddMemberDialogParams, ): params is EditMemberDialogParams { diff --git a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html index 12d7a920a2d..4a91fcc2a41 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html @@ -35,7 +35,7 @@ - + {{ "externalId" | i18n }} {{ "externalIdDesc" | i18n }} diff --git a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts index 07bff3aba64..70b26041df6 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts @@ -38,7 +38,6 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { DIALOG_DATA, @@ -135,7 +134,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { protected showOrgSelector = false; protected formGroup = this.formBuilder.group({ name: ["", [Validators.required, BitValidators.forbiddenCharacters(["/"])]], - externalId: "", + externalId: { value: "", disabled: true }, parent: undefined as string | undefined, access: [[] as AccessItemValue[]], selectedOrg: "", @@ -145,16 +144,6 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { protected showAddAccessWarning = false; protected collections: Collection[]; protected buttonDisplayName: ButtonType = ButtonType.Save; - protected isExternalIdVisible$ = this.configService - .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) - .pipe( - map((isEnabled) => { - return ( - !isEnabled || - (!!this.params.isAdminConsoleActive && !!this.formGroup.get("externalId")?.value) - ); - }), - ); private orgExceedingCollectionLimit!: Organization; constructor( @@ -165,7 +154,6 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private groupService: GroupApiService, private collectionAdminService: CollectionAdminService, private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, private organizationUserApiService: OrganizationUserApiService, private dialogService: DialogService, private changeDetectorRef: ChangeDetectorRef, @@ -354,6 +342,10 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { return this.formGroup.controls.selectedOrg; } + protected get isExternalIdVisible(): boolean { + return this.params.isAdminConsoleActive && !!this.formGroup.get("externalId")?.value; + } + protected get collectionId() { return this.params.collectionId; } @@ -490,23 +482,10 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private handleFormGroupReadonly(readonly: boolean) { if (readonly) { this.formGroup.controls.name.disable(); - this.formGroup.controls.externalId.disable(); this.formGroup.controls.parent.disable(); this.formGroup.controls.access.disable(); } else { this.formGroup.controls.name.enable(); - - this.configService - .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) - .pipe(takeUntil(this.destroy$)) - .subscribe((isEnabled) => { - if (isEnabled) { - this.formGroup.controls.externalId.disable(); - } else { - this.formGroup.controls.externalId.enable(); - } - }); - this.formGroup.controls.parent.enable(); this.formGroup.controls.access.enable(); } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index f9e68efe4be..dcd214c37d7 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -12,7 +12,6 @@ import { ServerConfig } from "../platform/abstractions/config/server-config"; export enum FeatureFlag { /* Admin Console Team */ LimitItemDeletion = "pm-15493-restrict-item-deletion-to-can-manage-permission", - SsoExternalIdVisibility = "pm-18630-sso-external-id-visibility", AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner", SeparateCustomRolePermissions = "pm-19917-separate-custom-role-permissions", @@ -83,7 +82,6 @@ const FALSE = false as boolean; export const DefaultFeatureFlagValue = { /* Admin Console Team */ [FeatureFlag.LimitItemDeletion]: FALSE, - [FeatureFlag.SsoExternalIdVisibility]: FALSE, [FeatureFlag.AccountDeprovisioningBanner]: FALSE, [FeatureFlag.SeparateCustomRolePermissions]: FALSE, From f52e4e27a030cb5ba9cc743f088c61bbebcbcc72 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Thu, 22 May 2025 11:09:33 -0500 Subject: [PATCH 138/245] [PM-12770] Assign to Collections Hint (#14529) * allow use of common spec in lib/vault tests * pass readonly collections to the assign collection component - The assign to collections component filters them out already. -They're also needed to display copy within the component * add hint to assign to collections component when there are read only collections assigned to a cipher already * add readonly hint to desktop * only show collection hint for collections that are assigned to the provided ciphers * consider admin/owner edit everything permission when assigning to collections * fix icon in test --- .../assign-collections.component.ts | 2 +- apps/desktop/src/locales/en/messages.json | 9 ++ .../collections/vault.component.ts | 3 +- .../vault/individual-vault/vault.component.ts | 2 +- libs/vault/jest.config.js | 10 +- .../assign-collections.component.html | 5 +- .../assign-collections.component.spec.ts | 113 ++++++++++++++++++ .../assign-collections.component.ts | 29 +++++ 8 files changed, 165 insertions(+), 8 deletions(-) create mode 100644 libs/vault/src/components/assign-collections.component.spec.ts diff --git a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts index 7052be5ea62..a11a7d806bd 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts @@ -74,7 +74,7 @@ export class AssignCollections { combineLatest([cipher$, this.collectionService.decryptedCollections$]) .pipe(takeUntilDestroyed(), first()) .subscribe(([cipherView, collections]) => { - let availableCollections = collections.filter((c) => !c.readOnly); + let availableCollections = collections; const organizationId = (cipherView?.organizationId as OrganizationId) ?? null; // If the cipher is already a part of an organization, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 35433e8e2e1..50b9ed4336c 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts index 96c00faceb2..a3b62838d6a 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts @@ -362,8 +362,7 @@ export class VaultComponent implements OnInit, OnDestroy { if (this.organization.canEditAllCiphers) { return collections; } - // The user is only allowed to add/edit items to assigned collections that are not readonly - return collections.filter((c) => c.assigned && !c.readOnly); + return collections.filter((c) => c.assigned); }), shareReplay({ refCount: true, bufferSize: 1 }), ); diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 55bbd0c0651..6e751f600dc 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -933,7 +933,7 @@ export class VaultComponent implements OnInit, OnDestroy { if (orgId && orgId !== "MyVault") { const organization = this.allOrganizations.find((o) => o.id === orgId); availableCollections = this.allCollections.filter( - (c) => c.organizationId === organization.id && !c.readOnly, + (c) => c.organizationId === organization.id, ); } diff --git a/libs/vault/jest.config.js b/libs/vault/jest.config.js index e33c115e8dd..16db37527ac 100644 --- a/libs/vault/jest.config.js +++ b/libs/vault/jest.config.js @@ -10,7 +10,11 @@ module.exports = { displayName: "libs/vault tests", preset: "jest-preset-angular", setupFilesAfterEnv: ["/test.setup.ts"], - moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "/", - }), + moduleNameMapper: pathsToModuleNameMapper( + // lets us use @bitwarden/common/spec in tests + { "@bitwarden/common/spec": ["../common/spec"], ...(compilerOptions?.paths ?? {}) }, + { + prefix: "/", + }, + ), }; diff --git a/libs/vault/src/components/assign-collections.component.html b/libs/vault/src/components/assign-collections.component.html index d68799eec6d..a82f2cb29a1 100644 --- a/libs/vault/src/components/assign-collections.component.html +++ b/libs/vault/src/components/assign-collections.component.html @@ -37,13 +37,16 @@
- + {{ "selectCollectionsToAssign" | i18n }} + + {{ "cannotRemoveViewOnlyCollections" | i18n: readOnlyCollectionNames.join(", ") }} +
diff --git a/libs/vault/src/components/assign-collections.component.spec.ts b/libs/vault/src/components/assign-collections.component.spec.ts new file mode 100644 index 00000000000..d6707cd1064 --- /dev/null +++ b/libs/vault/src/components/assign-collections.component.spec.ts @@ -0,0 +1,113 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { ProductTierType } from "@bitwarden/common/billing/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { ToastService } from "@bitwarden/components"; + +import { + AssignCollectionsComponent, + CollectionAssignmentParams, +} from "./assign-collections.component"; + +describe("AssignCollectionsComponent", () => { + let component: AssignCollectionsComponent; + let fixture: ComponentFixture; + + const mockUserId = "mock-user-id" as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); + + const editCollection = new CollectionView(); + editCollection.id = "collection-id" as CollectionId; + editCollection.organizationId = "org-id" as OrganizationId; + editCollection.name = "Editable Collection"; + editCollection.readOnly = false; + editCollection.manage = true; + + const readOnlyCollection1 = new CollectionView(); + readOnlyCollection1.id = "read-only-collection-id" as CollectionId; + readOnlyCollection1.organizationId = "org-id" as OrganizationId; + readOnlyCollection1.name = "Read Only Collection"; + readOnlyCollection1.readOnly = true; + + const readOnlyCollection2 = new CollectionView(); + readOnlyCollection2.id = "read-only-collection-id-2" as CollectionId; + readOnlyCollection2.organizationId = "org-id" as OrganizationId; + readOnlyCollection2.name = "Read Only Collection 2"; + readOnlyCollection2.readOnly = true; + + const params = { + organizationId: "org-id" as OrganizationId, + ciphers: [ + { + id: "cipher-id", + name: "Cipher Name", + collectionIds: [readOnlyCollection1.id], + edit: true, + } as unknown as CipherView, + ], + availableCollections: [editCollection, readOnlyCollection1, readOnlyCollection2], + } as CollectionAssignmentParams; + + const org = { + id: "org-id", + name: "Test Org", + productTierType: ProductTierType.Enterprise, + } as Organization; + + const organizations$ = jest.fn().mockReturnValue(of([org])); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + providers: [ + { provide: CipherService, useValue: mock() }, + { provide: OrganizationService, useValue: mock({ organizations$ }) }, + { provide: CollectionService, useValue: mock() }, + { provide: ToastService, useValue: mock() }, + { provide: AccountService, useValue: accountService }, + { provide: I18nService, useValue: { t: (...keys: string[]) => keys.join(" ") } }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(AssignCollectionsComponent); + component = fixture.componentInstance; + component.params = params; + fixture.detectChanges(); + }); + + describe("read only collections", () => { + beforeEach(async () => { + await component.ngOnInit(); + fixture.detectChanges(); + }); + + it("shows read-only hint for assigned collections", () => { + const hint = fixture.debugElement.query(By.css('[data-testid="view-only-hint"]')); + + expect(hint.nativeElement.textContent.trim()).toBe( + "cannotRemoveViewOnlyCollections Read Only Collection", + ); + }); + + it("does not show read only collections in the list", () => { + expect(component["availableCollections"]).toEqual([ + { + icon: "bwi-collection-shared", + id: editCollection.id, + labelName: editCollection.name, + listName: editCollection.name, + }, + ]); + }); + }); +}); diff --git a/libs/vault/src/components/assign-collections.component.ts b/libs/vault/src/components/assign-collections.component.ts index 6a0c45cfbe3..db62f096faa 100644 --- a/libs/vault/src/components/assign-collections.component.ts +++ b/libs/vault/src/components/assign-collections.component.ts @@ -126,6 +126,12 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI collections: [[], [Validators.required]], }); + /** + * Collections that are already assigned to the cipher and are read-only. These cannot be removed. + * @protected + */ + protected readOnlyCollectionNames: string[] = []; + protected totalItemCount: number; protected editableItemCount: number; protected readonlyItemCount: number; @@ -301,6 +307,8 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI this.organizationService.organizations$(userId).pipe(getOrganizationById(organizationId)), ); + await this.setReadOnlyCollectionNames(); + this.availableCollections = this.params.availableCollections .filter((collection) => { return collection.canEditItems(org); @@ -503,4 +511,25 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI await this.cipherService.saveCollectionsWithServer(cipher, userId); } } + + /** + * Only display collections that are read-only and are assigned to the ciphers. + */ + private async setReadOnlyCollectionNames() { + const { availableCollections, ciphers } = this.params; + + const organization = await firstValueFrom( + this.organizations$.pipe(map((orgs) => orgs.find((o) => o.id === this.selectedOrgId))), + ); + + this.readOnlyCollectionNames = availableCollections + .filter((c) => { + return ( + c.readOnly && + ciphers.some((cipher) => cipher.collectionIds.includes(c.id)) && + !c.canEditItems(organization) + ); + }) + .map((c) => c.name); + } } From 753e7af380b53ec111bad58e13b442a713c6db4c Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Thu, 22 May 2025 12:29:15 -0400 Subject: [PATCH 139/245] [PM-21882] Lit Components Cleanup (#14872) * rename item row component to cipher item row * move CipherItem into CipherItemRow instead of passing a generic children prop * remove redundant embedded stories * add mock data to dropdown button story --- .../lit-stories/.lit-docs/cipher-action.mdx | 83 ----------------- .../lit-stories/.lit-docs/cipher-icon.mdx | 90 ------------------- .../.lit-docs/cipher-indicator-icon.mdx | 81 ----------------- .../ciphers/cipher-action.lit-stories.ts | 31 ------- .../ciphers/cipher-icon.lit-stories.ts | 33 ------- .../cipher-indicator-icons.lit-stories.ts | 28 ------ .../ciphers/cipher-info.lit-stories.ts | 23 ----- .../rows/button-row.lit-stories.ts | 51 +++++++++++ .../cipher-item-row.lit-stories.ts} | 20 ++--- .../lit-stories/rows/item-row.lit-stories.ts | 22 ----- .../content/components/notification/body.ts | 16 ++-- .../components/rows/cipher-item-row.ts | 78 ++++++++++++++++ .../content/components/rows/item-row.ts | 55 ------------ 13 files changed, 145 insertions(+), 466 deletions(-) delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-action.mdx delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-icon.mdx delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-indicator-icon.mdx delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-action.lit-stories.ts delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-icon.lit-stories.ts delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icons.lit-stories.ts delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-info.lit-stories.ts rename apps/browser/src/autofill/content/components/lit-stories/{ciphers/cipher-item.lit-stories.ts => rows/cipher-item-row.lit-stories.ts} (62%) delete mode 100644 apps/browser/src/autofill/content/components/lit-stories/rows/item-row.lit-stories.ts create mode 100644 apps/browser/src/autofill/content/components/rows/cipher-item-row.ts delete mode 100644 apps/browser/src/autofill/content/components/rows/item-row.ts diff --git a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-action.mdx b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-action.mdx deleted file mode 100644 index 3b5dcd8797a..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-action.mdx +++ /dev/null @@ -1,83 +0,0 @@ -import { Meta, Controls, Primary } from "@storybook/addon-docs"; - -import * as stories from "./cipher-action.lit-stories"; - - - -## Cipher Action - -The `CipherAction` component is a functional UI element that handles actions related to ciphers in a -secure environment. Built with the `lit` library and styled for consistency across themes, it -provides flexibility and accessibility while supporting various notification types. - - - - -## Props - -| **Prop** | **Type** | **Required** | **Description** | -| ------------------ | --------------------------------------------------- | ------------ | -------------------------------------------------------------- | -| `handleAction` | `(e: Event) => void` | No | Function to execute when an action is triggered. | -| `notificationType` | `NotificationTypes.Change \| NotificationTypes.Add` | Yes | Specifies the type of notification associated with the action. | -| `theme` | `Theme` | Yes | The theme to style the component. Must match the `Theme` enum. | - -## Installation and Setup - -1. Ensure the necessary dependencies are installed: - - - `lit`: Used to render the component. - -2. Pass the required props when rendering the component: - - `handleAction`: Optional function to handle the triggered action. - - `notificationType`: Mandatory type from `NotificationTypes` to define the action context. - - `theme`: The styling theme (must be a valid `Theme` enum value). - -## Accessibility (WCAG) Compliance - -The `CipherAction` component is designed to be accessible, ensuring usability across diverse user -bases. Below are the key considerations for accessibility: - -### Keyboard Accessibility - -- Fully navigable using the keyboard. -- The action can be triggered using the `Enter` or `Space` key for users relying on keyboard - interaction. - -### Screen Reader Compatibility - -- The semantic elements used in the `CipherAction` component ensure that assistive technologies can - interpret the component correctly. -- Text associated with the `notificationType` is programmatically linked, providing clarity for - screen reader users. - -### Focus Management - -- The component includes focus styles to ensure visibility during navigation. -- Proper focus management ensures the component works seamlessly with keyboard navigation. - -### Visual Feedback - -- Provides distinct visual states for different themes and states: - - **Hover:** Adjustments to background, border, and text for enhanced visibility. - - **Active:** Highlights the button with a focus state when activated. - - **Disabled:** Grays out the component to indicate inactivity. - -## Usage Example - -Here's an example of how to integrate the `CipherAction` component: - -```ts -import { CipherAction } from "../../cipher/cipher-action"; -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; -import { NotificationTypes } from "../../../../notification/abstractions/notification-bar"; - -const handleAction = (e: Event) => { - console.log("Cipher action triggered!", e); -}; - -; -``` diff --git a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-icon.mdx b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-icon.mdx deleted file mode 100644 index a1a94efde7b..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-icon.mdx +++ /dev/null @@ -1,90 +0,0 @@ -import { Meta, Controls, Primary } from "@storybook/addon-docs"; - -import * as stories from "./cipher-icon.lit-stories"; - - - -## Cipher Icon - -The `CipherIcon` component is a versatile icon renderer designed for secure environments. It -dynamically supports custom icons provided via URIs or displays a default icon (`Globe`) styled -based on the theme and provided properties. - - - - -## Props - -| **Prop** | **Type** | **Required** | **Description** | -| -------- | ------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `color` | `string` | Yes | A contextual color override applied when the `uri` is not provided, ensuring consistent styling of the default icon. | -| `size` | `string` | Yes | A valid CSS `width` value representing the width basis of the graphic. The height adjusts to maintain the original aspect ratio of the graphic. | -| `theme` | `Theme` | Yes | The styling theme for the icon, matching the `Theme` enum. | -| `uri` | `string` (optional) | No | A URL to an external graphic. If provided, the component displays this icon. If omitted, a default icon (`Globe`) styled with the provided `color` and `theme`. | - -## Installation and Setup - -1. Ensure the necessary dependencies are installed: - - - `lit`: Renders the component. - - `@emotion/css`: Styles the component. - -2. Pass the necessary props when using the component: - - `color`: Used when no `uri` is provided to style the default icon. - - `size`: Defines the width of the icon. Height maintains aspect ratio. - - `theme`: Specifies the theme for styling. - - `uri` (optional): If provided, this URI is used to display a custom icon. - -## Accessibility (WCAG) Compliance - -The `CipherIcon` component ensures accessible and user-friendly interactions through thoughtful -design: - -### Semantic Rendering - -- When the `uri` is provided, the component renders an `` element, which is semantically - appropriate for external graphics. -- If no `uri` is provided, the default icon is wrapped in a ``, ensuring proper context for - screen readers. - -### Visual Feedback - -- The component visually adjusts based on the `size`, `color`, and `theme`, ensuring the icon - remains clear and legible across different environments. - -### Keyboard and Screen Reader Support - -- Ensure that any container or parent component provides appropriate `alt` text or labeling when - `uri` is used with an `` tag for additional accessibility. - -## Usage Example - -Here's an example of how to integrate the `CipherIcon` component: - -```ts -import { CipherIcon } from "./cipher-icon"; -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; - -; -``` - -This configuration displays a custom icon from the provided URI with a width of 32px, styled for the -light theme. If the URI is omitted, the Globe icon is used as the fallback, colored in blue. - -### Default Styles - -- The default styles ensure responsive and clean design: - -- Width: Defined by the size prop. -- Height: Automatically adjusts to maintain the aspect ratio. -- Fit Content: Ensures the icon does not overflow or distort its container. - -### Notes - -- Always validate the uri provided to ensure it points to a secure and accessible location. -- Use the color and theme props for consistent fallback styling when uri is not provided. diff --git a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-indicator-icon.mdx b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-indicator-icon.mdx deleted file mode 100644 index 6c338276c02..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/cipher-indicator-icon.mdx +++ /dev/null @@ -1,81 +0,0 @@ -import { Meta, Controls, Primary } from "@storybook/addon-docs"; - -import * as stories from "./cipher-indicator-icon.lit-stories"; - - - -## Cipher Info Indicator Icons - -The `CipherInfoIndicatorIcons` component displays a set of icons indicating specific attributes -related to cipher information. It supports business and family organization indicators, styled -dynamically based on the provided theme. - - - - -## Props - -| **Prop** | **Type** | **Required** | **Description** | -| ------------------ | --------- | ------------ | ----------------------------------------------------------------------- | -| `showBusinessIcon` | `boolean` | No | Displays the business organization icon when set to `true`. | -| `showFamilyIcon` | `boolean` | No | Displays the family organization icon when set to `true`. | -| `theme` | `Theme` | Yes | Defines the theme used to style the icons. Must match the `Theme` enum. | - -## Installation and Setup - -1. Ensure the necessary dependencies are installed: - - - `lit`: Renders the component. - - `@emotion/css`: Used for styling. - -2. Pass the required props when using the component: - - `showBusinessIcon`: A boolean that, when `true`, displays the business icon. - - `showFamilyIcon`: A boolean that, when `true`, displays the family icon. - - `theme`: Specifies the theme for styling the icons. - -## Accessibility (WCAG) Compliance - -The `CipherInfoIndicatorIcons` component ensures accessibility and usability through its design: - -### Screen Reader Compatibility - -- Icons are rendered as `` elements, and parent components should provide appropriate labeling - or descriptions to convey their meaning to screen readers. - -### Visual Feedback - -- Icons are styled dynamically based on the `theme` to ensure visual clarity and contrast in all - supported themes. -- The size of the icons is fixed at `12px` in height to maintain a consistent visual appearance. - -## Usage Example - -Here's an example of how to integrate the `CipherInfoIndicatorIcons` component: - -```ts -import { CipherInfoIndicatorIcons } from "./cipher-info-indicator-icons"; -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; - -; -``` - -This example displays the business organization icon, styled for the dark theme, and omits the -family organization icon. - -### Styling Details - -- The component includes the following styles: - -- Icons: Rendered as SVGs with a height of 12px and a width that adjusts to maintain their aspect - ratio. -- Color: Icons are dynamically styled based on the theme, using muted text colors for a subtle - appearance. - -### Notes - -- If neither showBusinessIcon nor showFamilyIcon is set to true, the component renders nothing. This - behavior should be handled by the parent component. diff --git a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-action.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-action.lit-stories.ts deleted file mode 100644 index 99b7e9e0acb..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-action.lit-stories.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Meta, StoryObj } from "@storybook/web-components"; - -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; - -import { NotificationTypes } from "../../../../notification/abstractions/notification-bar"; -import { CipherAction, CipherActionProps } from "../../cipher/cipher-action"; -import { mockI18n } from "../mock-data"; - -export default { - title: "Components/Ciphers/Cipher Action", - argTypes: { - theme: { control: "select", options: [...Object.values(ThemeTypes)] }, - notificationType: { - control: "select", - options: [NotificationTypes.Change, NotificationTypes.Add], - }, - handleAction: { control: false }, - }, - args: { - theme: ThemeTypes.Light, - notificationType: NotificationTypes.Change, - handleAction: () => alert("Action triggered!"), - i18n: mockI18n, - }, -} as Meta; - -const Template = (args: CipherActionProps) => CipherAction({ ...args }); - -export const Default: StoryObj = { - render: Template, -}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-icon.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-icon.lit-stories.ts deleted file mode 100644 index d0396b013c8..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-icon.lit-stories.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Meta, StoryObj } from "@storybook/web-components"; -import { html } from "lit"; - -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; - -import { CipherIcon, CipherIconProps } from "../../cipher/cipher-icon"; - -export default { - title: "Components/Ciphers/Cipher Icon", - argTypes: { - color: { control: "color" }, - size: { control: "text" }, - theme: { control: "select", options: [...Object.values(ThemeTypes)] }, - uri: { control: "text" }, - }, - args: { - size: "50px", - theme: ThemeTypes.Light, - uri: "", - }, -} as Meta; - -const Template = (args: CipherIconProps) => { - return html` -
- ${CipherIcon({ ...args })} -
- `; -}; - -export const Default: StoryObj = { - render: Template, -}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icons.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icons.lit-stories.ts deleted file mode 100644 index 7b10aeee8de..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-indicator-icons.lit-stories.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Meta, StoryObj } from "@storybook/web-components"; -import { html } from "lit"; - -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; - -import { - CipherInfoIndicatorIcons, - CipherInfoIndicatorIconsProps, -} from "../../cipher/cipher-indicator-icons"; -import { OrganizationCategories } from "../../cipher/types"; - -export default { - title: "Components/Ciphers/Cipher Indicator Icons", - argTypes: { - theme: { control: "select", options: [...Object.values(ThemeTypes)] }, - }, - args: { - theme: ThemeTypes.Light, - organizationCategories: [...Object.values(OrganizationCategories)], - }, -} as Meta; - -const Template: StoryObj["render"] = (args) => - html`
${CipherInfoIndicatorIcons({ ...args })}
`; - -export const Default: StoryObj = { - render: Template, -}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-info.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-info.lit-stories.ts deleted file mode 100644 index b56c3193bb8..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-info.lit-stories.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Meta, StoryObj } from "@storybook/web-components"; - -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; - -import { CipherInfo, CipherInfoProps } from "../../cipher/cipher-info"; -import { mockCiphers } from "../mock-data"; - -export default { - title: "Components/Ciphers/Cipher Info", - argTypes: { - theme: { control: "select", options: [...Object.values(ThemeTypes)] }, - }, - args: { - cipher: mockCiphers[0], - theme: ThemeTypes.Light, - }, -} as Meta; - -const Template = (args: CipherInfoProps) => CipherInfo({ ...args }); - -export const Default: StoryObj = { - render: Template, -}; diff --git a/apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts index 83b498df7cb..343f36d5e11 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts +++ b/apps/browser/src/autofill/content/components/lit-stories/rows/button-row.lit-stories.ts @@ -4,6 +4,7 @@ import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; import { themes } from "../../constants/styles"; import { ButtonRow, ButtonRowProps } from "../../rows/button-row"; +import { mockBrowserI18nGetMessage } from "../mock-data"; export default { title: "Components/Rows/Button Row", @@ -15,6 +16,49 @@ export default { window.alert("Button clicked!"); }, }, + selectButtons: [ + { + id: "select-1", + label: "select 1", + options: [ + { + text: "item 1", + value: 1, + }, + { + default: true, + text: "item 2", + value: 2, + }, + { + text: "item 3", + value: 3, + }, + ], + }, + { + id: "select-2", + label: "select 2", + options: [ + { + text: "item a", + value: "a", + }, + { + text: "item b", + value: "b", + }, + { + text: "item c", + value: "c", + }, + { + text: "item d", + value: "d", + }, + ], + }, + ], }, } as Meta; @@ -51,3 +95,10 @@ export const Dark: StoryObj = { }, }, }; + +window.chrome = { + ...window.chrome, + i18n: { + getMessage: mockBrowserI18nGetMessage, + }, +} as typeof chrome; diff --git a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-item.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/rows/cipher-item-row.lit-stories.ts similarity index 62% rename from apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-item.lit-stories.ts rename to apps/browser/src/autofill/content/components/lit-stories/rows/cipher-item-row.lit-stories.ts index 67915db0a7b..59c38c56745 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/ciphers/cipher-item.lit-stories.ts +++ b/apps/browser/src/autofill/content/components/lit-stories/rows/cipher-item-row.lit-stories.ts @@ -3,30 +3,30 @@ import { Meta, StoryObj } from "@storybook/web-components"; import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; import { NotificationTypes } from "../../../../notification/abstractions/notification-bar"; -import { CipherItem, CipherItemProps } from "../../cipher/cipher-item"; +import { CipherItemRow, CipherItemRowProps } from "../../rows/cipher-item-row"; import { mockCiphers, mockI18n } from "../mock-data"; export default { - title: "Components/Ciphers/Cipher Item", + title: "Components/Rows/Cipher Item Row", argTypes: { theme: { control: "select", options: [...Object.values(ThemeTypes)] }, - handleAction: { control: false }, notificationType: { control: "select", - options: [NotificationTypes.Change, NotificationTypes.Add], + options: [...Object.values(NotificationTypes)], }, + handleAction: { control: false }, }, args: { cipher: mockCiphers[0], - theme: ThemeTypes.Light, - notificationType: NotificationTypes.Change, - handleAction: () => alert("Clicked"), i18n: mockI18n, + notificationType: NotificationTypes.Change, + theme: ThemeTypes.Light, + handleAction: () => window.alert("clicked!"), }, -} as Meta; +} as Meta; -const Template = (args: CipherItemProps) => CipherItem({ ...args }); +const Template = (props: CipherItemRowProps) => CipherItemRow({ ...props }); -export const Default: StoryObj = { +export const Default: StoryObj = { render: Template, }; diff --git a/apps/browser/src/autofill/content/components/lit-stories/rows/item-row.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/rows/item-row.lit-stories.ts deleted file mode 100644 index 375b793d016..00000000000 --- a/apps/browser/src/autofill/content/components/lit-stories/rows/item-row.lit-stories.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Meta, StoryObj } from "@storybook/web-components"; - -import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; - -import { ItemRow, ItemRowProps } from "../../rows/item-row"; - -export default { - title: "Components/Rows/Item Row", - argTypes: { - theme: { control: "select", options: [...Object.values(ThemeTypes)] }, - children: { control: "object" }, - }, - args: { - theme: ThemeTypes.Light, - }, -} as Meta; - -const Template = (args: ItemRowProps) => ItemRow({ ...args }); - -export const Default: StoryObj = { - render: Template, -}; diff --git a/apps/browser/src/autofill/content/components/notification/body.ts b/apps/browser/src/autofill/content/components/notification/body.ts index 4d8019b0a55..b1ce7cdba63 100644 --- a/apps/browser/src/autofill/content/components/notification/body.ts +++ b/apps/browser/src/autofill/content/components/notification/body.ts @@ -4,11 +4,10 @@ import { html } from "lit"; import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; import { NotificationType } from "../../../notification/abstractions/notification-bar"; -import { CipherItem } from "../cipher"; import { NotificationCipherData } from "../cipher/types"; import { I18n } from "../common-types"; import { scrollbarStyles, spacing, themes, typography } from "../constants/styles"; -import { ItemRow } from "../rows/item-row"; +import { CipherItemRow } from "../rows/cipher-item-row"; export const componentClassPrefix = "notification-body"; @@ -37,15 +36,12 @@ export function NotificationBody({ return html`
${ciphers.map((cipher) => - ItemRow({ + CipherItemRow({ + cipher, theme, - children: CipherItem({ - cipher, - i18n, - notificationType, - theme, - handleAction: handleEditOrUpdateAction, - }), + i18n, + notificationType, + handleAction: handleEditOrUpdateAction, }), )}
diff --git a/apps/browser/src/autofill/content/components/rows/cipher-item-row.ts b/apps/browser/src/autofill/content/components/rows/cipher-item-row.ts new file mode 100644 index 00000000000..0600fc9ac4b --- /dev/null +++ b/apps/browser/src/autofill/content/components/rows/cipher-item-row.ts @@ -0,0 +1,78 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { NotificationType } from "../../../notification/abstractions/notification-bar"; +import { CipherItem } from "../cipher/cipher-item"; +import { NotificationCipherData } from "../cipher/types"; +import { I18n } from "../common-types"; +import { spacing, themes, typography } from "../constants/styles"; + +export type CipherItemRowProps = { + cipher: NotificationCipherData; + i18n: I18n; + notificationType?: NotificationType; + theme: Theme; + handleAction: (e: Event) => void; +}; + +export function CipherItemRow({ + cipher, + i18n, + notificationType, + theme, + handleAction, +}: CipherItemRowProps) { + return html` +
+ ${CipherItem({ + cipher, + i18n, + notificationType, + theme, + handleAction, + })} +
+ `; +} + +const cipherItemRowStyles = ({ theme }: { theme: Theme }) => css` + ${typography.body1} + + gap: ${spacing["2"]}; + display: flex; + align-items: center; + justify-content: space-between; + border-width: 0 0 0.5px 0; + border-style: solid; + border-radius: ${spacing["2"]}; + border-color: ${themes[theme].secondary["300"]}; + background-color: ${themes[theme].background.DEFAULT}; + padding: ${spacing["2"]} ${spacing["3"]}; + min-height: min-content; + max-height: 52px; + overflow-x: hidden; + white-space: nowrap; + color: ${themes[theme].text.main}; + font-weight: 400; + + > div { + :first-child { + flex: 3 3 75%; + min-width: 25%; + } + + :not(:first-child) { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-end; + max-width: 25%; + + > button { + max-width: min-content; + } + } + } +`; diff --git a/apps/browser/src/autofill/content/components/rows/item-row.ts b/apps/browser/src/autofill/content/components/rows/item-row.ts deleted file mode 100644 index 8e9a870002e..00000000000 --- a/apps/browser/src/autofill/content/components/rows/item-row.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { css } from "@emotion/css"; -import { html, TemplateResult } from "lit"; - -import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; - -import { spacing, themes, typography } from "../../../content/components/constants/styles"; - -export type ItemRowProps = { - theme: Theme; - children: TemplateResult | TemplateResult[]; -}; - -export function ItemRow({ theme = ThemeTypes.Light, children }: ItemRowProps) { - return html`
${children}
`; -} - -export const itemRowStyles = ({ theme }: { theme: Theme }) => css` - ${typography.body1} - - gap: ${spacing["2"]}; - display: flex; - align-items: center; - justify-content: space-between; - border-width: 0 0 0.5px 0; - border-style: solid; - border-radius: ${spacing["2"]}; - border-color: ${themes[theme].secondary["300"]}; - background-color: ${themes[theme].background.DEFAULT}; - padding: ${spacing["2"]} ${spacing["3"]}; - min-height: min-content; - max-height: 52px; - overflow-x: hidden; - white-space: nowrap; - color: ${themes[theme].text.main}; - font-weight: 400; - - > div { - :first-child { - flex: 3 3 75%; - min-width: 25%; - } - - :not(:first-child) { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - justify-content: flex-end; - max-width: 25%; - - > button { - max-width: min-content; - } - } - } -`; From 57911f210bcebb68557c31633ae8c9ff2c277731 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Thu, 22 May 2025 10:20:33 -0700 Subject: [PATCH 140/245] [PM-21896] - prevent double reprompt for copy password in desktop cipher form (#14883) * prevent double reprompt for copy password in desktop cipher form * adjust name * fix input name --- apps/desktop/src/vault/app/vault/vault.component.html | 1 + apps/desktop/src/vault/app/vault/view.component.ts | 4 ++++ libs/angular/src/vault/components/view.component.ts | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/vault/app/vault/vault.component.html b/apps/desktop/src/vault/app/vault/vault.component.html index 99131a848cc..9a25619b1a8 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.html +++ b/apps/desktop/src/vault/app/vault/vault.component.html @@ -15,6 +15,7 @@ *ngIf="cipherId && action === 'view'" [cipherId]="cipherId" [collectionId]="activeFilter?.selectedCollectionId" + [masterPasswordAlreadyPrompted]="cipherRepromptId === cipherId" (onCloneCipher)="cloneCipherWithoutPasswordPrompt($event)" (onEditCipher)="editCipher($event)" (onViewCipherPasswordHistory)="viewCipherPasswordHistory($event)" diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index 084a9a747ed..68bf8d6d1a3 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -3,6 +3,7 @@ import { ChangeDetectorRef, Component, EventEmitter, + Input, NgZone, OnChanges, OnDestroy, @@ -46,6 +47,7 @@ const BroadcasterSubscriptionId = "ViewComponent"; }) export class ViewComponent extends BaseViewComponent implements OnInit, OnDestroy, OnChanges { @Output() onViewCipherPasswordHistory = new EventEmitter(); + @Input() masterPasswordAlreadyPrompted: boolean = false; constructor( cipherService: CipherService, @@ -120,6 +122,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro } }); }); + this.passwordReprompted = this.masterPasswordAlreadyPrompted; } ngOnDestroy() { @@ -134,6 +137,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro }); return; } + this.passwordReprompted = this.masterPasswordAlreadyPrompted; } viewHistory() { diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 8915cb6b671..fd3f92c6c73 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -95,7 +95,7 @@ export class ViewComponent implements OnDestroy, OnInit { cipherType = CipherType; private previousCipherId: string; - private passwordReprompted = false; + protected passwordReprompted = false; /** * Represents TOTP information including display formatting and timing From bd29397fd8859a6bf48b40f769682d835979823e Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Thu, 22 May 2025 13:55:26 -0500 Subject: [PATCH 141/245] [PM-21611] Require userId on KeyService clear methods (#14788) --- .../settings/account-security.component.ts | 3 +- .../service-container/service-container.ts | 2 +- .../src/app/accounts/settings.component.ts | 3 +- apps/web/src/app/app.component.ts | 2 +- .../vault-timeout-settings.service.ts | 2 +- .../vault-timeout-settings.service.ts | 2 +- .../src/abstractions/key.service.ts | 6 ++- libs/key-management/src/key.service.spec.ts | 44 +++++++++++-------- libs/key-management/src/key.service.ts | 12 ++--- 9 files changed, 41 insertions(+), 35 deletions(-) diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index d7b8aa573d4..26a805b3624 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -480,7 +480,8 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { }); await this.vaultNudgesService.dismissNudge(NudgeType.AccountSecurity, userId); } else { - await this.vaultTimeoutSettingsService.clear(); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.vaultTimeoutSettingsService.clear(userId); } } diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index cdf6c4bbfda..bc5db09da26 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -874,7 +874,7 @@ export class ServiceContainer { const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await Promise.all([ this.eventUploadService.uploadEvents(userId as UserId), - this.keyService.clearKeys(), + this.keyService.clearKeys(userId), this.cipherService.clear(userId), this.folderService.clear(userId), this.collectionService.clear(userId), diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 83c982fbaba..fd0585e805e 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -506,7 +506,8 @@ export class SettingsComponent implements OnInit, OnDestroy { await this.updateRequirePasswordOnStart(); } - await this.vaultTimeoutSettingsService.clear(); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.vaultTimeoutSettingsService.clear(userId); } this.messagingService.send("redrawMenu"); diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index cac0487d05d..3de9bf0a8c8 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -282,7 +282,7 @@ export class AppComponent implements OnDestroy, OnInit { ); await Promise.all([ - this.keyService.clearKeys(), + this.keyService.clearKeys(userId), this.cipherService.clear(userId), this.folderService.clear(userId), this.collectionService.clear(userId), diff --git a/libs/common/src/key-management/vault-timeout/abstractions/vault-timeout-settings.service.ts b/libs/common/src/key-management/vault-timeout/abstractions/vault-timeout-settings.service.ts index 7094a2c2f83..9ff362e4009 100644 --- a/libs/common/src/key-management/vault-timeout/abstractions/vault-timeout-settings.service.ts +++ b/libs/common/src/key-management/vault-timeout/abstractions/vault-timeout-settings.service.ts @@ -59,5 +59,5 @@ export abstract class VaultTimeoutSettingsService { */ isBiometricLockSet: (userId?: string) => Promise; - clear: (userId?: string) => Promise; + clear: (userId: UserId) => Promise; } diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts index 0716bf0bb93..07b8a8c297d 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts @@ -287,7 +287,7 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA return availableActions; } - async clear(userId?: string): Promise { + async clear(userId: UserId): Promise { await this.keyService.clearPinKeys(userId); } diff --git a/libs/key-management/src/abstractions/key.service.ts b/libs/key-management/src/abstractions/key.service.ts index d67fec4c98e..95b79890c6a 100644 --- a/libs/key-management/src/abstractions/key.service.ts +++ b/libs/key-management/src/abstractions/key.service.ts @@ -370,8 +370,9 @@ export abstract class KeyService { * Note: This will remove the stored pin and as a result, * disable pin protection for the user * @param userId The desired user + * @throws Error when provided userId is null or undefined */ - abstract clearPinKeys(userId?: string): Promise; + abstract clearPinKeys(userId: UserId): Promise; /** * @param keyMaterial The key material to derive the send key from * @returns A new send key @@ -380,8 +381,9 @@ export abstract class KeyService { /** * Clears all of the user's keys from storage * @param userId The user's Id + * @throws Error when provided userId is null or undefined */ - abstract clearKeys(userId?: string): Promise; + abstract clearKeys(userId: UserId): Promise; abstract randomNumber(min: number, max: number): Promise; /** * Generates a new cipher key diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index 25d8aff99fb..f1f1286dfc5 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -1,5 +1,5 @@ import { mock } from "jest-mock-extended"; -import { BehaviorSubject, bufferCount, firstValueFrom, lastValueFrom, of, take, tap } from "rxjs"; +import { BehaviorSubject, bufferCount, firstValueFrom, lastValueFrom, of, take } from "rxjs"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data"; @@ -380,16 +380,12 @@ describe("keyService", () => { }); describe("clearKeys", () => { - it("resolves active user id when called with no user id", async () => { - let callCount = 0; - stateProvider.activeUserId$ = stateProvider.activeUserId$.pipe(tap(() => callCount++)); - - await keyService.clearKeys(); - expect(callCount).toBe(1); - - // revert to the original state - accountService.activeAccount$ = accountService.activeAccountSubject.asObservable(); - }); + test.each([null as unknown as UserId, undefined as unknown as UserId])( + "throws when the provided userId is %s", + async (userId) => { + await expect(keyService.clearKeys(userId)).rejects.toThrow("UserId is required"); + }, + ); describe.each([ USER_ENCRYPTED_ORGANIZATION_KEYS, @@ -397,14 +393,6 @@ describe("keyService", () => { USER_ENCRYPTED_PRIVATE_KEY, USER_KEY, ])("key removal", (key: UserKeyDefinition) => { - it(`clears ${key.key} for active user when unspecified`, async () => { - await keyService.clearKeys(); - - const encryptedOrgKeyState = stateProvider.singleUser.getFake(mockUserId, key); - expect(encryptedOrgKeyState.nextMock).toHaveBeenCalledTimes(1); - expect(encryptedOrgKeyState.nextMock).toHaveBeenCalledWith(null); - }); - it(`clears ${key.key} for the specified user when specified`, async () => { const userId = "someOtherUser" as UserId; await keyService.clearKeys(userId); @@ -416,6 +404,24 @@ describe("keyService", () => { }); }); + describe("clearPinKeys", () => { + test.each([null as unknown as UserId, undefined as unknown as UserId])( + "throws when the provided userId is %s", + async (userId) => { + await expect(keyService.clearPinKeys(userId)).rejects.toThrow("UserId is required"); + }, + ); + it("calls pin service to clear", async () => { + const userId = "someOtherUser" as UserId; + + await keyService.clearPinKeys(userId); + + expect(pinService.clearPinKeyEncryptedUserKeyPersistent).toHaveBeenCalledWith(userId); + expect(pinService.clearPinKeyEncryptedUserKeyEphemeral).toHaveBeenCalledWith(userId); + expect(pinService.clearUserKeyEncryptedPin).toHaveBeenCalledWith(userId); + }); + }); + describe("userPrivateKey$", () => { type SetupKeysParams = { makeMasterKey: boolean; diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index 849b9db6a50..9372dafd3ea 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -564,11 +564,9 @@ export class DefaultKeyService implements KeyServiceAbstraction { await this.stateProvider.setUserState(USER_ENCRYPTED_PRIVATE_KEY, null, userId); } - async clearPinKeys(userId?: UserId): Promise { - userId ??= await firstValueFrom(this.stateProvider.activeUserId$); - + async clearPinKeys(userId: UserId): Promise { if (userId == null) { - throw new Error("Cannot clear PIN keys, no user Id resolved."); + throw new Error("UserId is required"); } await this.pinService.clearPinKeyEncryptedUserKeyPersistent(userId); @@ -588,11 +586,9 @@ export class DefaultKeyService implements KeyServiceAbstraction { return (await this.keyGenerationService.createKey(512)) as CipherKey; } - async clearKeys(userId?: UserId): Promise { - userId ??= await firstValueFrom(this.stateProvider.activeUserId$); - + async clearKeys(userId: UserId): Promise { if (userId == null) { - throw new Error("Cannot clear keys, no user Id resolved."); + throw new Error("UserId is required"); } await this.masterPasswordService.clearMasterKeyHash(userId); From 5702a17772f44b60ea6637efd4c34a6ced12ac82 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 11:57:58 +0200 Subject: [PATCH 142/245] Autosync the updated translations (#14892) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 50 +- apps/browser/src/_locales/az/messages.json | 50 +- apps/browser/src/_locales/be/messages.json | 50 +- apps/browser/src/_locales/bg/messages.json | 50 +- apps/browser/src/_locales/bn/messages.json | 50 +- apps/browser/src/_locales/bs/messages.json | 50 +- apps/browser/src/_locales/ca/messages.json | 52 +- apps/browser/src/_locales/cs/messages.json | 50 +- apps/browser/src/_locales/cy/messages.json | 50 +- apps/browser/src/_locales/da/messages.json | 50 +- apps/browser/src/_locales/de/messages.json | 50 +- apps/browser/src/_locales/el/messages.json | 50 +- apps/browser/src/_locales/en_GB/messages.json | 50 +- apps/browser/src/_locales/en_IN/messages.json | 50 +- apps/browser/src/_locales/es/messages.json | 76 +- apps/browser/src/_locales/et/messages.json | 50 +- apps/browser/src/_locales/eu/messages.json | 50 +- apps/browser/src/_locales/fa/messages.json | 1310 +++++++++-------- apps/browser/src/_locales/fi/messages.json | 92 +- apps/browser/src/_locales/fil/messages.json | 50 +- apps/browser/src/_locales/fr/messages.json | 50 +- apps/browser/src/_locales/gl/messages.json | 50 +- apps/browser/src/_locales/he/messages.json | 50 +- apps/browser/src/_locales/hi/messages.json | 50 +- apps/browser/src/_locales/hr/messages.json | 50 +- apps/browser/src/_locales/hu/messages.json | 50 +- apps/browser/src/_locales/id/messages.json | 50 +- apps/browser/src/_locales/it/messages.json | 50 +- apps/browser/src/_locales/ja/messages.json | 50 +- apps/browser/src/_locales/ka/messages.json | 50 +- apps/browser/src/_locales/km/messages.json | 50 +- apps/browser/src/_locales/kn/messages.json | 50 +- apps/browser/src/_locales/ko/messages.json | 50 +- apps/browser/src/_locales/lt/messages.json | 92 +- apps/browser/src/_locales/lv/messages.json | 50 +- apps/browser/src/_locales/ml/messages.json | 50 +- apps/browser/src/_locales/mr/messages.json | 50 +- apps/browser/src/_locales/my/messages.json | 50 +- apps/browser/src/_locales/nb/messages.json | 50 +- apps/browser/src/_locales/ne/messages.json | 50 +- apps/browser/src/_locales/nl/messages.json | 50 +- apps/browser/src/_locales/nn/messages.json | 50 +- apps/browser/src/_locales/or/messages.json | 50 +- apps/browser/src/_locales/pl/messages.json | 50 +- apps/browser/src/_locales/pt_BR/messages.json | 50 +- apps/browser/src/_locales/pt_PT/messages.json | 50 +- apps/browser/src/_locales/ro/messages.json | 50 +- apps/browser/src/_locales/ru/messages.json | 78 +- apps/browser/src/_locales/si/messages.json | 50 +- apps/browser/src/_locales/sk/messages.json | 52 +- apps/browser/src/_locales/sl/messages.json | 50 +- apps/browser/src/_locales/sr/messages.json | 102 +- apps/browser/src/_locales/sv/messages.json | 50 +- apps/browser/src/_locales/te/messages.json | 50 +- apps/browser/src/_locales/th/messages.json | 50 +- apps/browser/src/_locales/tr/messages.json | 50 +- apps/browser/src/_locales/uk/messages.json | 50 +- apps/browser/src/_locales/vi/messages.json | 50 +- apps/browser/src/_locales/zh_CN/messages.json | 64 +- apps/browser/src/_locales/zh_TW/messages.json | 50 +- 60 files changed, 3254 insertions(+), 1214 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index c96f4a5803b..48ae14fc1ce 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "لم يتم العثور على معرف فريد." }, - "convertOrganizationEncryptionDesc": { - "message": "يستخدم $ORGANIZATION$ SSO مع خادم مفتاح الاستضافة الذاتية. لم تعد هناك حاجة إلى كلمة مرور رئيسية لتسجيل الدخول لأعضاء هذه المنظمة.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "مغادرة المؤسسة" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 51eb1345466..c9096bce0d6 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Unikal identifikator tapılmadı." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$, self-hosted açar serveri ilə SSO istifadə edir. Bu təşkilatın üzvlərinin giriş etməsi üçün artıq ana parol tələb edilməyəcək.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Aşağıdakı təşkilatların üzvləri üçün artıq ana parol tələb olunmur. Lütfən aşağıdakı domeni təşkilatınızın inzibatçısı ilə təsdiqləyin." + }, + "organizationName": { + "message": "Təşkilat adı" + }, + "keyConnectorDomain": { + "message": "Key Connector domeni" }, "leaveOrganization": { "message": "Təşkilatı tərk et" @@ -3615,6 +3615,14 @@ "message": "Şifrələnmiş məlumatları hər kəslə güvənli şəkildə paylaşmaq üçün \"Send\"i istifadə edin.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send, həssas məlumatlar təhlükəsizdir", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "İstənilən platformada faylları və dataları hər kəslə paylaşın. İfşa olunmağı məhdudlaşdıraraq məlumatlarınız ucdan-uca şifrələnmiş qalacaq.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Giriş lazımdır." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Bilinməyən bilinməyən bir səbəbə görə biometrik kilid açma əlçatmazdır." }, + "unlockVault": { + "message": "Seyfinizin kilidini saniyələr ərzində açın" + }, + "unlockVaultDesc": { + "message": "Seyfinizə daha cəld müraciət etmək üçün kilid açma və bitmə vaxtı ayarlarını özəlləşdirə bilərsiniz." + }, + "unlockPinSet": { + "message": "PIN ilə kilid açma təyini" + }, "authenticating": { "message": "Kimlik doğrulama" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Cəld parol yaradın" + }, + "generatorNudgeBodyOne": { + "message": "Klikləyərək güclü və unikal parolları asanlıqla yaradın", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "və girişlərinizi güvənli şəkildə saxlayın.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Girişlərinizi güvənli şəkildə saxlamağınıza kömək etməsi üçün Parol yarat düyməsinə klikləyərək güclü və unikal parolları asanlıqla yaradın.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Bu səhifəyə baxmaq icazəniz yoxdur. Fərqli hesabla giriş etməyə çalışın." } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index a51b96547da..0dd5ff4163f 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Не знойдзены ўнікальны ідэнтыфікатар." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ выкарыстоўвае SSO з уласным серверам ключоў. Асноўны пароль для ўдзельнікаў гэтай арганізацыі больш не патрабуецца.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Выйсці з арганізацыі" @@ -3615,6 +3615,14 @@ "message": "Выкарыстоўвайце Send'ы, каб бяспечна абагуляць зашыфраваную інфармацыю з іншымі.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Неабходны ўвод даных." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 09e5260f9b3..0bf8dbce2b0 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Няма намерен уникален идентификатор." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ използва еднократно удостоверяване със собствен сървър за ключове. Членовете на тази организация вече нямат нужда от главна парола за вписване.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "За членовете на следната организация вече не се изисква главна парола. Потвърдете домейна по-долу с администратора на организацията си." + }, + "organizationName": { + "message": "Име на организацията" + }, + "keyConnectorDomain": { + "message": "Домейн на конектора за ключове" }, "leaveOrganization": { "message": "Напускане на организацията" @@ -3615,6 +3615,14 @@ "message": "Използвайте Изпращане, за да споделите безопасно шифрована информация с някого.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Изпращайте чувствителна информация сигурно", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Споделяйте сигурно файлове и данни с всекиго, през всяка система. Информацията Ви ще бъде защитена с шифроване от край до край, а видимостта ѝ ще бъде ограничена.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Полето е задължително да бъде попълнено." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Отключването с биометрични данни не е налично по неизвестна причина." }, + "unlockVault": { + "message": "Отключвайте трезора си за секунди" + }, + "unlockVaultDesc": { + "message": "Можете да персонализирате настройките си за отключване и време на активност, за да получавате достъп до трезора си по-бързо." + }, + "unlockPinSet": { + "message": "Зададен е ПИН код за отключване" + }, "authenticating": { "message": "Удостоверяване" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Създавайте пароли бързо" + }, + "generatorNudgeBodyOne": { + "message": "Създавайте лесно сложни и уникални пароли като щракнете върху", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "за да защитите данните си за вписване.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Създавайте лесно сложни и уникални пароли като щракнете върху бутона за генериране на парола, за да защитите данните си за вписване.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Нямате права за преглед на тази страница. Опитайте да се впишете с друг акаунт." } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 5e19936e975..f7f28115faa 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index f48037814e5..1dc35addd8e 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 151412c02ed..9517257ac3a 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -1057,7 +1057,7 @@ "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "notificationAddDesc": { - "message": "Bitwarden ha de recordar aquesta contrasenya per a vosaltres?" + "message": "Ha de recordar Bitwarden aquesta contrasenya per a vosaltres?" }, "notificationAddSave": { "message": "Guarda" @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No s'ha trobat cap identificador únic." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ està utilitzant SSO amb un servidor autoallotjat de claus. Ja no es requereix una contrasenya mestra d'inici de sessió per als membres d'aquesta organització.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Abandona l'organització" @@ -3615,6 +3615,14 @@ "message": "Utilitzeu Send per compartir informació xifrada de manera segura amb qualsevol persona.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "L'entrada és obligatòria." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "S'està autenticant" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index effc4c950c6..b213e0aee7d 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nenalezen žádný jedinečný identifikátor." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ používá SSO s vlastním serverem s klíči. Hlavní heslo pro členy této organizace již není vyžadováno.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hlavní heslo již není vyžadováno pro členy následující organizace. Potvrďte níže uvedenou doménu u správce Vaší organizace." + }, + "organizationName": { + "message": "Název organizace" + }, + "keyConnectorDomain": { + "message": "Doména Key Connectoru" }, "leaveOrganization": { "message": "Opustit organizaci" @@ -3615,6 +3615,14 @@ "message": "Použijte Send pro bezpečné sdílení šifrovaných informací s kýmkoliv.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Posílejte citlivé informace bezpečně", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Sdílejte bezpečně soubory a data s kýmkoli na libovolné platformě. Vaše informace zůstanou šifrovány a zároveň omezují expozici.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Je vyžadován vstup." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrické odemknutí je momentálně z neznámého důvodu nedostupné." }, + "unlockVault": { + "message": "Rychlé odemknutí trezoru" + }, + "unlockVaultDesc": { + "message": "Pro rychlejší přístup k trezoru můžete upravit nastavení odemknutí a vypršení časového limitu." + }, + "unlockPinSet": { + "message": "PIN pro odemknutí byl nastaven" + }, "authenticating": { "message": "Ověřování" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Rychlé vytvoření hesla" + }, + "generatorNudgeBodyOne": { + "message": "Jednoduše vytvořte silná a unikátní hesla klepnutím na", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "aby Vám pomohlo udržet Vaše přihlašovací údaje v bezpečí.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Jednoduše vytvořte silná a unikátní hesla klepnutím na tlačítko Generovat heslo, které Vám pomůže udržet Vaše přihlašovací údaje v bezpečí.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Nemáte oprávnění k zobrazení této stránky. Zkuste se přihlásit jiným účtem." } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 047b45dd9b8..ee74d1e45e2 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index e6233b1f8db..da24e1bcfb9 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Ingen entydig identifikator fundet." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ bruger SSO med en selv-hostet nøgleserver. En hovedadgangskode er ikke længere påkrævet for at logge ind for medlemmer af denne organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Forlad organisation" @@ -3615,6 +3615,14 @@ "message": "Brug Send til at dele krypterede oplysninger sikkert med nogen.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input obligatorisk." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrisk oplåsning er p.t. utilgængelig grundet en ukendt årsag." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Godkender" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 1eac22c919c..f7303885551 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Keine eindeutige Kennung gefunden." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ verwendet SSO mit einem selbst gehosteten Schlüsselserver. Ein Master-Passwort ist nicht mehr erforderlich, damit sich Mitglieder dieser Organisation anmelden können.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Für Mitglieder der folgenden Organisation ist kein Master-Passwort mehr erforderlich. Bitte bestätige die folgende Domain bei deinem Organisations-Administrator." + }, + "organizationName": { + "message": "Name der Organisation" + }, + "keyConnectorDomain": { + "message": "Key Connector-Domain" }, "leaveOrganization": { "message": "Organisation verlassen" @@ -3615,6 +3615,14 @@ "message": "Verwende Send, um verschlüsselte Informationen sicher mit anderen zu teilen.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Sensible Informationen sicher versenden", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Teile Dateien und Daten sicher mit jedem auf jeder Plattform. Deine Informationen bleiben Ende-zu-Ende-Verschlüsselt, während die Verbreitung begrenzt wird.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Eingabe ist erforderlich." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrisches Entsperren ist derzeit aus einem unbekannten Grund nicht verfügbar." }, + "unlockVault": { + "message": "Entsperre deinen Tresor in Sekunden" + }, + "unlockVaultDesc": { + "message": "Du kannst deine Entsperr- und Timeout-Einstellungen anpassen, um schneller auf deinen Tresor zuzugreifen." + }, + "unlockPinSet": { + "message": "Entsperr-PIN festgelegt" + }, "authenticating": { "message": "Authentifizierung" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Passwörter schnell erstellen" + }, + "generatorNudgeBodyOne": { + "message": "Generiere ganz einfach starke und einzigartige Passwörter, indem du auf den", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": ", um deine Zugangsdaten sicher aufzubewahren.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Generiere ganz einfach starke und einzigartige Passwörter, indem du auf den \"Passwort generieren\"-Button klickst, um deine Zugangsdaten sicher aufzubewahren.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Du hast keine Berechtigung, diese Seite anzuzeigen. Versuche dich mit einem anderen Konto anzumelden." } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index faad1a90a07..ed81dfa744e 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Δε βρέθηκε μοναδικό αναγνωριστικό." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ χρησιμοποιεί SSO με έναν αυτοεξυπηρετητή κλειδιών. Ένας κύριος κωδικός πρόσβασης δεν απαιτείται πλέον για να συνδεθείτε για τα μέλη αυτού του οργανισμού.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Αποχώρηση από τον οργανισμό" @@ -3615,6 +3615,14 @@ "message": "Χρήση Send για ασφαλή κοινοποίηση κρυπτογραφημένων πληροφοριών με οποιονδήποτε.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Απαιτείται εισαγωγή." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Ταυτοποίηση" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index d131d3ec33d..5eecf989894 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." + }, + "organizationName": { + "message": "Organisation name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organisation" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customise your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index aa1c076b2e9..adb3cf6ec32 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." + }, + "organizationName": { + "message": "Organisation name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave Organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customise your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 3018cd36ed4..94d02848b5f 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -3,7 +3,7 @@ "message": "Bitwarden" }, "appLogoLabel": { - "message": "Bitwarden logo" + "message": "Logo de Bitwarden" }, "extName": { "message": "Bitwarden - Administrador de contraseñas", @@ -656,7 +656,7 @@ "message": "Tu navegador web no soporta copiar al portapapeles facilmente. Cópialo manualmente." }, "verifyYourIdentity": { - "message": "Verify your identity" + "message": "Verifica tu identidad" }, "weDontRecognizeThisDevice": { "message": "No reconocemos este dispositivo. Introduce el código enviado a tu correo electrónico para verificar tu identidad." @@ -872,16 +872,16 @@ "message": "Iniciar sesión en Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "Introduce el código enviado a tu correo electrónico" }, "enterTheCodeFromYourAuthenticatorApp": { "message": "Introduce el código de tu aplicación de autenticación" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "Pulsa tu YubiKey para identificarte" }, "duoTwoFactorRequiredPageSubtitle": { - "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + "message": "Se requiere inicio de sesión en dos pasos para tu cuenta. Sigue los pasos siguientes para terminar de iniciar sesión." }, "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." @@ -911,7 +911,7 @@ "message": "No" }, "location": { - "message": "Location" + "message": "Ubicación" }, "unexpectedError": { "message": "Ha ocurrido un error inesperado." @@ -1063,7 +1063,7 @@ "message": "Guardar" }, "notificationViewAria": { - "message": "View $ITEMNAME$, opens in new window", + "message": "Ver $ITEMNAME$, se abre en una nueva ventana", "placeholders": { "itemName": { "content": "$1" @@ -1072,7 +1072,7 @@ "description": "Aria label for the view button in notification bar confirmation message" }, "notificationNewItemAria": { - "message": "New Item, opens in new window", + "message": "Nuevo Elemento, se abre en una nueva ventana", "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" }, "notificationEditTooltip": { @@ -2594,7 +2594,7 @@ "message": "Illustration of the Bitwarden autofill menu displaying a generated password." }, "updateInBitwarden": { - "message": "Update in Bitwarden" + "message": "Actualizar en Bitwarden" }, "updateInBitwardenSlideDesc": { "message": "Bitwarden will then prompt you to update the password in the password manager.", @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Identificador único no encontrado." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ está usando SSO con un servidor de claves autoalojado. Los miembros de esta organización ya no necesitarán una contraseña maestra para iniciar sesión.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Ya no es necesaria una contraseña maestra para los miembros de la siguiente organización. Confirma el dominio que aparece a continuación con el administrador de tu organización." + }, + "organizationName": { + "message": "Nombre de la organización" + }, + "keyConnectorDomain": { + "message": "Dominio del conector de clave" }, "leaveOrganization": { "message": "Abandonar organización" @@ -3406,7 +3406,7 @@ "message": "Inicio de sesión en proceso" }, "logInRequestSent": { - "message": "Request sent" + "message": "Solicitud enviada" }, "exposedMasterPassword": { "message": "Contraseña maestra comprometida" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Entrada requerida." }, @@ -3817,7 +3825,7 @@ "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "Nueva tarjeta", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { @@ -4553,10 +4561,10 @@ "message": "Download from bitwarden.com now" }, "getItOnGooglePlay": { - "message": "Get it on Google Play" + "message": "Consíguela en Google Play" }, "downloadOnTheAppStore": { - "message": "Download on the App Store" + "message": "Descarga en la App Store" }, "permanentlyDeleteAttachmentConfirmation": { "message": "¿Estás seguro de que deseas eliminar permanentemente este adjunto?" @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autenticando" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 5bce2142219..26b4bf69fb4 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Unikaalset identifikaatorit ei leitud." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ kasutab SSO-d koos enda majutatud võtmeserveriga. Selle organisatsiooni liikmed ei pea sisselogimisel enam ülemparooli kasutama.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lahku organisatsioonist" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Sisestus on nõutav." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 54b7b9b234c..b3f525d7be5 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Ez da identifikatzaile bakarrik aurkitu." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ SSO erabiltzen ari da ostatatze propioa duen gako-zerbitzari batekin. Dagoeneko ez da pasahitz nagusirik behar erakunde honetako kideentzat saioa hasteko.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Utzi erakundea" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index f132b61fc4e..33a779a7909 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -6,7 +6,7 @@ "message": "لوگو Bitwarden" }, "extName": { - "message": "مدیریت رمز عبور Bitwarden", + "message": "مدیریت کلمه عبور Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { @@ -35,10 +35,10 @@ "message": "خوش آمدید" }, "setAStrongPassword": { - "message": "تنظیم رمز عبور قوی" + "message": "تنظیم کلمه عبور قوی" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "ایجاد حساب خود را با تنظیم رمز عبور تکمیل کنید" + "message": "ایجاد حساب کاربری خود را با تنظیم کلمه عبور تکمیل کنید" }, "enterpriseSingleSignOn": { "message": "ورود به سیستم پروژه" @@ -303,7 +303,7 @@ "message": "درباره استفاده از Bitwarden در مرکز راهنما بیشتر بیاموزید." }, "continueToBrowserExtensionStore": { - "message": "آیا میخواهید به فروشگاه افزونه مرورگر ادامه دهید?" + "message": "آیا می‌خواهید به فروشگاه افزونه مرورگر ادامه دهید؟" }, "continueToBrowserExtensionStoreDesc": { "message": "به دیگران کمک کنید تا بفهمند آیا Bitwarden برایشان مناسب است یا نه. به فروشگاه افزونه مرورگر خود بروید و نظر خود را به اشتراک بگذارید." @@ -341,7 +341,7 @@ "message": "Bitwarden برای کسب و کارها" }, "bitwardenAuthenticator": { - "message": "تاییدکننده هویت Bitwarden" + "message": "تأییدکننده هویت Bitwarden" }, "continueToAuthenticatorPageDesc": { "message": "احراز هویت کننده Bitwarden به شما امکان می‌دهد کلیدهای احراز هویت را ذخیره کرده و کدهای TOTP را برای فرآیندهای تأیید دومرحله‌ای تولید کنید. برای اطلاعات بیشتر به وب‌سایت bitwarden.com مراجعه کنید" @@ -796,7 +796,7 @@ "message": "حساب کاربری جدید شما ساخته شد! حالا می‌توانید وارد شوید." }, "newAccountCreated2": { - "message": "حساب جدید شما ایجاد شده است!" + "message": "حساب کاربری جدید شما ایجاد شده است!" }, "youHaveBeenLoggedIn": { "message": "شما با موفقیت وارد شدید!" @@ -839,55 +839,55 @@ "message": "کلید احراز هویت اضافه شد" }, "totpCapture": { - "message": "Scan authenticator QR code from current webpage" + "message": "اسکن کد QR احراز هویت کننده از صفحه وب فعلی" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "تأیید دو مرحله‌ای را بدون دردسر کنید" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden می‌تواند کدهای تأیید دو مرحله‌ای را ذخیره و پر کند. کلید را کپی کرده و در این فیلد قرار دهید." }, "totpHelperWithCapture": { - "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + "message": "Bitwarden می‌تواند کدهای تأیید دو مرحله‌ای را ذخیره و پر کند. برای اسکن کد QR احراز هویت کننده این وب‌سایت، روی آیکون دوربین کلیک کنید یا کلید را کپی کرده و در این فیلد قرار دهید." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "درباره احراز هویت کننده‌ها بیشتر بدانید" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "کپی کلید احراز هویت (TOTP)" }, "loggedOut": { "message": "خارج شد" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "شما از حساب خود خارج شده‌اید." }, "loginExpired": { "message": "نشست ورود شما منقضی شده است." }, "logIn": { - "message": "Log in" + "message": "ورود" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "وارد Bitwarden شوید" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "کدی را که به ایمیل شما ارسال شده وارد کنید" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "کد سامانه تأیید کننده را وارد نمایید" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "برای احراز هویت، کلید YubiKey خود را فشار دهید" }, "duoTwoFactorRequiredPageSubtitle": { - "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + "message": "برای حساب کاربری شما ورود دو مرحله‌ای Duo لازم است. مراحل زیر را دنبال کنید تا ورود خود را کامل کنید." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Follow the steps below to finish logging in." + "message": "مراحل زیر را دنبال کنید تا وارد سیستم شوید." }, "followTheStepsBelowToFinishLoggingInWithSecurityKey": { - "message": "Follow the steps below to finish logging in with your security key." + "message": "مراحل زیر را برای کامل کردن ورود با کلید امنیتی خود دنبال کنید." }, "restartRegistration": { "message": "ثبت‌نام را دوباره آغاز کنید" @@ -896,10 +896,10 @@ "message": "پیوند منقضی شد" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "لطفاً ثبت نام را مجدداً شروع کنید یا دوباره وارد شوید." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "ممکن است قبلاً حساب کاربری داشته باشید" }, "logOutConfirmation": { "message": "آیا مطمئنید که می‌خواهید خارج شوید؟" @@ -926,10 +926,10 @@ "message": "ورود دو مرحله ای باعث می‌شود که حساب کاربری شما با استفاده از یک دستگاه دیگر مانند کلید امنیتی، برنامه احراز هویت، پیامک، تماس تلفنی و یا ایمیل، اعتبار خود را با ایمنی بیشتر اثبات کند. ورود دو مرحله ای می تواند در bitwarden.com فعال شود. آیا می‌خواهید از سایت بازدید کنید؟" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "با راه‌اندازی ورود دو مرحله‌ای در برنامه وب Bitwarden، حساب کاربری خود را ایمن‌تر کنید." }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "با برنامه وب ادامه می‌دهید؟" }, "editedFolder": { "message": "پوشه ذخیره شد" @@ -1016,16 +1016,16 @@ "message": "درخواست افزودن ورود به سیستم" }, "vaultSaveOptionsTitle": { - "message": "Save to vault options" + "message": "گزینه‌های ذخیره در گاوصندوق" }, "addLoginNotificationDesc": { "message": "در صورتی که موردی در گاوصندوق شما یافت نشد، درخواست افزودن کنید." }, "addLoginNotificationDescAlt": { - "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." + "message": "اگر موردی در گاوصندوق شما یافت نشد، درخواست افزودن آن را بدهید. این مورد برای همه حساب‌های وارد شده اعمال می‌شود." }, "showCardsInVaultViewV2": { - "message": "Always show cards as Autofill suggestions on Vault view" + "message": "همیشه کارت‌ها را به‌عنوان پیشنهادهای پر کردن خودکار در نمای گاوصندوق نمایش بده" }, "showCardsCurrentTab": { "message": "نمایش کارت‌ها در صفحه برگه" @@ -1034,7 +1034,7 @@ "message": "برای پر کردن خودکار آسان، موارد کارت را در صفحه برگه فهرست کن." }, "showIdentitiesInVaultViewV2": { - "message": "Always show identities as Autofill suggestions on Vault view" + "message": "همیشه هویت‌ها را به‌عنوان پیشنهادهای پر کردن خودکار در نمای گاوصندوق نمایش بده" }, "showIdentitiesCurrentTab": { "message": "نشان دادن هویت در صفحه برگه" @@ -1043,10 +1043,10 @@ "message": "موارد هویتی را در صفحه برگه برای پر کردن خودکار آسان فهرست کن." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "برای پر کردن خودکار، روی موردها در نمای گاوصندوق کلیک کنید" }, "clickToAutofill": { - "message": "Click items in autofill suggestion to fill" + "message": "برای پر کردن، روی موردها در پیشنهادهای پرکردن خودکار کلیک کنید" }, "clearClipboard": { "message": "پاکسازی کلیپ بورد", @@ -1063,7 +1063,7 @@ "message": "ذخیره" }, "notificationViewAria": { - "message": "View $ITEMNAME$, opens in new window", + "message": "مشاهده $ITEMNAME$، در پنجره جدید باز می‌شود", "placeholders": { "itemName": { "content": "$1" @@ -1072,18 +1072,18 @@ "description": "Aria label for the view button in notification bar confirmation message" }, "notificationNewItemAria": { - "message": "New Item, opens in new window", + "message": "مورد جدید، در پنجره جدید باز می‌شود", "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" }, "notificationEditTooltip": { - "message": "Edit before saving", + "message": "ویرایش قبل ذخیره کردن", "description": "Tooltip and Aria label for edit button on cipher item" }, "newNotification": { - "message": "New notification" + "message": "اعلان جدید" }, "labelWithNotification": { - "message": "$LABEL$: New notification", + "message": "$LABEL$: اعلان جدید", "description": "Label for the notification with a new login suggestion.", "placeholders": { "label": { @@ -1093,15 +1093,15 @@ } }, "notificationLoginSaveConfirmation": { - "message": "saved to Bitwarden.", + "message": "در Bitwarden ذخیره شد.", "description": "Shown to user after item is saved." }, "notificationLoginUpdatedConfirmation": { - "message": "updated in Bitwarden.", + "message": "در Bitwarden به‌روزرسانی شد.", "description": "Shown to user after item is updated." }, "selectItemAriaLabel": { - "message": "Select $ITEMTYPE$, $ITEMNAME$", + "message": "انتخاب $ITEMTYPE$، $ITEMNAME$", "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", "placeholders": { "itemType": { @@ -1113,35 +1113,35 @@ } }, "saveAsNewLoginAction": { - "message": "Save as new login", + "message": "ذخیره به عنوان ورود جدید", "description": "Button text for saving login details as a new entry." }, "updateLoginAction": { - "message": "Update login", + "message": "به‌روزرسانی ورود", "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "برای ذخیره این ورود، قفل را باز کنید", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { - "message": "Save login", + "message": "ذخیره ورود", "description": "Prompt asking the user if they want to save their login details." }, "updateLogin": { - "message": "Update existing login", + "message": "به‌روزرسانی ورود به سیستم موجود", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { - "message": "Login saved", + "message": "ورود ذخیره شد", "description": "Message displayed when login details are successfully saved." }, "loginUpdateSuccess": { - "message": "Login updated", + "message": "ورود به‌روزرسانی شد", "description": "Message displayed when login details are successfully updated." }, "loginUpdateTaskSuccess": { - "message": "Great job! You took the steps to make you and $ORGANIZATION$ more secure.", + "message": "آفرین! شما اقداماتی را انجام دادید تا خودتان و $ORGANIZATION$ را ایمن‌تر کنید.", "placeholders": { "organization": { "content": "$1" @@ -1150,7 +1150,7 @@ "description": "Shown to user after login is updated." }, "loginUpdateTaskSuccessAdditional": { - "message": "Thank you for making $ORGANIZATION$ more secure. You have $TASK_COUNT$ more passwords to update.", + "message": "ممنون که $ORGANIZATION$ را ایمن‌تر کردید. شما $TASK_COUNT$ کلمات عبور دیگر برای به‌روزرسانی دارید.", "placeholders": { "organization": { "content": "$1" @@ -1162,15 +1162,15 @@ "description": "Shown to user after login is updated." }, "nextSecurityTaskAction": { - "message": "Change next password", + "message": "تغییر کلمه عبور بعدی", "description": "Message prompting user to undertake completion of another security task." }, "saveFailure": { - "message": "Error saving", + "message": "خطای ذخیره", "description": "Error message shown when the system fails to save login details." }, "saveFailureDetails": { - "message": "Oh no! We couldn't save this. Try entering the details manually.", + "message": "اوه نه! نتوانستیم این را ذخیره کنیم. لطفاً جزئیات را به‌صورت دستی وارد کنید.", "description": "Detailed error message shown when saving login details fails." }, "enableChangedPasswordNotification": { @@ -1180,13 +1180,13 @@ "message": "هنگامی که تغییری در یک وب‌سایت شناسایی شد، درخواست به‌روزرسانی کلمه عبور ورود کن." }, "changedPasswordNotificationDescAlt": { - "message": "Ask to update a login's password when a change is detected on a website. Applies to all logged in accounts." + "message": "هنگامی که تغییری در کلمه عبور یک ورود در وب‌سایت شناسایی شود، درخواست به‌روزرسانی آن را بده. این مورد برای همه حساب‌های وارد شده اعمال می‌شود." }, "enableUsePasskeys": { "message": "برای ذخیره و استفاده از passkey اجازه بگیر" }, "usePasskeysDesc": { - "message": "Ask to save new passkeys or log in with passkeys stored in your vault. Applies to all logged in accounts." + "message": "درخواست ذخیره کلیدهای عبور جدید یا ورود با کلیدهای عبوری که در گاوصندوق شما ذخیره شده‌اند. این مورد برای همه حساب‌های وارد شده اعمال می‌شود." }, "notificationChangeDesc": { "message": "آیا مایل به به‌روزرسانی این کلمه عبور در Bitwarden هستید؟" @@ -1210,7 +1210,7 @@ "message": "از یک کلیک ثانویه برای دسترسی به تولید کلمه عبور و ورودهای منطبق برای وب سایت استفاده کن." }, "contextMenuItemDescAlt": { - "message": "Use a secondary click to access password generation and matching logins for the website. Applies to all logged in accounts." + "message": "برای دسترسی به تولید کلمه عبور و ورودهای منطبق با وب‌سایت، از کلیک ثانویه استفاده کنید. این مورد برای همه حساب‌های کاربری وارد شده اعمال می‌شود." }, "defaultUriMatchDetection": { "message": "بررسی مطابقت نشانی اینترنتی پیش‌فرض", @@ -1226,7 +1226,7 @@ "message": "تغییر رنگ پوسته برنامه." }, "themeDescAlt": { - "message": "Change the application's color theme. Applies to all logged in accounts." + "message": "تغییر تم رنگی برنامه. این مورد برای همه حساب‌های کاربری وارد شده اعمال می‌شود." }, "dark": { "message": "تاریک", @@ -1237,7 +1237,7 @@ "description": "Light color" }, "exportFrom": { - "message": "صادرات از" + "message": "برون ریزی از" }, "exportVault": { "message": "برون ریزی گاوصندوق" @@ -1246,35 +1246,35 @@ "message": "فرمت پرونده" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "این پرونده برون ریزی با کلمه عبور محافظت می‌شود و برای رمزگشایی به کلمه عبور پرونده نیاز دارد." }, "filePassword": { - "message": "رمز فایل" + "message": "کلمه عبور پرونده" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "این کلمه عبور برای برون ریزی و درون ریزی این پرونده استفاده می‌شود" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "برای رمزگذاری برون ریزی و محدود کردن درون ریزی فقط به حساب کاربری فعلی Bitwarden، از کلید رمزگذاری حساب خود که از نام کاربری و کلمه عبور اصلی حساب شما مشتق شده است استفاده کنید." }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "یک کلمه عبور برای پرونده به‌منظور رمزگذاری تنظیم کنید تا برون ریزی و درون ریزی آن به هر حساب Bitwarden با استفاده از کلمه عبور رمزگشایی شود." }, "exportTypeHeading": { - "message": "نوع صادرات" + "message": "نوع برون ریزی" }, "accountRestricted": { "message": "حساب کاربری محدود شده است" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "عدم تطابق \"رمز فایل\" و \"تایید رمز فایل\" با یکدیگر." + "message": "عدم تطابق \"کلمه عبور پرونده\" و \"تأیید کلمه عبور پرونده\" با یکدیگر." }, "warning": { "message": "اخطار", "description": "WARNING (should stay in capitalized letters if the language permits)" }, "warningCapitalized": { - "message": "Warning", + "message": "هشدار", "description": "Warning (should maintain locale-relevant capitalization)" }, "confirmVaultExport": { @@ -1296,7 +1296,7 @@ "message": "اشتراک گذاری شد" }, "bitwardenForBusinessPageDesc": { - "message": "Bitwarden for Business allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website." + "message": "Bitwarden for Business به شما اجازه می‌دهد تا با استفاده از یک سازمان، موارد گاوصندوق خود را با دیگران به اشتراک بگذارید. در وب سایت bitwarden.com بیشتر بیاموزید." }, "moveToOrganization": { "message": "انتقال به سازمان" @@ -1354,7 +1354,7 @@ "message": "پرونده" }, "fileToShare": { - "message": "File to share" + "message": "پرونده‌ای برای اشتراک‌گذاری" }, "selectFile": { "message": "ﺍﻧﺘﺨﺎﺏ یک ﭘﺮﻭﻧﺪﻩ" @@ -1390,13 +1390,13 @@ "message": "۱ گیگابایت فضای ذخیره سازی رمزگذاری شده برای پیوست های پرونده." }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "دسترسی اضطراری." }, "premiumSignUpTwoStepOptions": { "message": "گزینه های ورود اضافی دو مرحله ای مانند YubiKey و Duo." }, "ppremiumSignUpReports": { - "message": "گزارش‌های بهداشت رمز عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما." + "message": "گزارش‌های بهداشت کلمه عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما." }, "ppremiumSignUpTotp": { "message": "تولید کننده کد تأیید (2FA) از نوع TOTP برای ورودهای در گاوصندوقتان." @@ -1411,7 +1411,7 @@ "message": "خرید پرمیوم" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "می‌توانید نسخه پرمیوم را از تنظیمات حساب کاربری خود در اپلیکیشن وب Bitwarden خریداری کنید." }, "premiumCurrentMember": { "message": "شما یک عضو پرمیوم هستید!" @@ -1420,7 +1420,7 @@ "message": "برای حمایتتان از Bitwarden سپاسگزاریم." }, "premiumFeatures": { - "message": "Upgrade to Premium and receive:" + "message": "ارتقا به نسخه پرمیوم و دریافت:" }, "premiumPrice": { "message": "تمامش فقط $PRICE$ در سال!", @@ -1432,7 +1432,7 @@ } }, "premiumPriceV2": { - "message": "All for just $PRICE$ per year!", + "message": "تمامش فقط $PRICE$ در سال!", "placeholders": { "price": { "content": "$1", @@ -1459,10 +1459,10 @@ "message": "برای استفاده از این ویژگی عضویت پرمیوم لازم است." }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "پایان زمان احراز هویت" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "نشست احراز هویت منقضی شد. لطفاً فرایند ورود را دوباره شروع کنید." }, "verificationCodeEmailSent": { "message": "ایمیل تأیید به $EMAIL$ ارسال شد.", @@ -1474,29 +1474,29 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "در این دستگاه به مدت ۳۰ روز دوباره نپرس" }, "selectAnotherMethod": { - "message": "Select another method", + "message": "انتخاب روش دیگر", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "از کد بازیابی‌تان استفاده کنید" }, "insertU2f": { "message": "کلید امنیتی خود را وارد پورت USB رایانه کنید، اگر دکمه ای دارد آن را بفشارید." }, "openInNewTab": { - "message": "Open in new tab" + "message": "گشودن در زبانهٔ جدید" }, "webAuthnAuthenticate": { "message": "تأیید اعتبار در WebAuthn" }, "readSecurityKey": { - "message": "Read security key" + "message": "خواندن کلید امنیتی" }, "awaitingSecurityKeyInteraction": { - "message": "Awaiting security key interaction..." + "message": "در انتظار تعامل با کلید امنیتی..." }, "loginUnavailable": { "message": "ورود به سیستم در دسترس نیست" @@ -1511,7 +1511,7 @@ "message": "گزینه‌های ورود دو مرحله‌ای" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "انتخاب ورود دو مرحله‌ای" }, "recoveryCodeDesc": { "message": "دسترسی به تمامی ارائه‌دهندگان دو مرحله‌ای را از دست داده‌اید؟ از کد بازیابی خود برای غیرفعال‌سازی ارائه‌دهندگان دو مرحله‌ای از حسابتان استفاده کنید." @@ -1523,17 +1523,17 @@ "message": "برنامه احراز هویت" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "کدی را وارد کنید که توسط یک برنامه احراز هویت مانند Bitwarden Authenticator تولید شده است.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP Security Key" + "message": "کلید امنیت Yubico OTP" }, "yubiKeyDesc": { "message": "از یک YubiKey برای دسترسی به حسابتان استفاده کنید. همراه با دستگاه‌های YubiKey 4 ،4 Nano ،NEO کار می‌کند." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "کدی را وارد کنید که توسط Duo Security تولید شده است.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -1550,19 +1550,19 @@ "message": "ایمیل" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "کدی را که به ایمیل شما ارسال شده وارد کنید." }, "selfHostedEnvironment": { "message": "محیط خود میزبان" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "نشانی اینترنتی پایه نصب Bitwarden خود را که به‌صورت داخلی میزبانی شده مشخص کنید.\nمثال: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "برای پیکربندی پیشرفته، می‌توانید نشانی اینترنتی پایه هر سرویس را به‌صورت مستقل مشخص کنید." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "شما باید یا نشانی اینترنتی پایه سرور را اضافه کنید، یا حداقل یک محیط سفارشی تعریف کنید." }, "customEnvironment": { "message": "محیط سفارشی" @@ -1571,7 +1571,7 @@ "message": "نشانی اینترنتی سرور" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "نشانی اینترنتی سرور خود میزبان", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1593,20 +1593,20 @@ "message": "نشانی‌های اینترنتی محیط ذخیره شد" }, "showAutoFillMenuOnFormFields": { - "message": "Show autofill menu on form fields", + "message": "نمایش منوی پر کردن خودکار روی فیلدهای فرم", "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "پیشنهادهای پر کردن خودکار" }, "autofillSpotlightTitle": { - "message": "Easily find autofill suggestions" + "message": "یافتن آسان پیشنهادهای پر کردن خودکار" }, "autofillSpotlightDesc": { - "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + "message": "تنظیمات پر کردن خودکار مرورگر خود را غیرفعال کنید تا با Bitwarden تداخل نداشته باشد." }, "turnOffBrowserAutofill": { - "message": "Turn off $BROWSER$ autofill", + "message": "پر کردن خودکار $BROWSER$ را غیرفعال کنید", "placeholders": { "browser": { "content": "$1", @@ -1615,25 +1615,25 @@ } }, "turnOffAutofill": { - "message": "Turn off autofill" + "message": "پر کردن خودکار را خاموش کنید" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "نمایش پیشنهادهای پر کردن خودکار روی فیلدهای فرم" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "نمایش هویت‌ها به‌عنوان پیشنهاد" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "نمایش کارت‌ها به‌عنوان پیشنهاد" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "نمایش پیشنهادها هنگام انتخاب آیکون" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "برای تمام حساب‌های کاربری وارد شده اعمال می‌شود." }, "turnOffBrowserBuiltInPasswordManagerSettings": { - "message": "Turn off your browser's built in password manager settings to avoid conflicts." + "message": "تنظیمات مدیر کلمه عبور داخلی مرورگر خود را غیرفعال کنید تا از بروز تداخل جلوگیری شود." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { "message": "ویرایش تنظیمات مرورگر." @@ -1643,15 +1643,15 @@ "description": "Overlay setting select option for disabling autofill overlay" }, "autofillOverlayVisibilityOnFieldFocus": { - "message": "When field is selected (on focus)", + "message": "وقتی فیلد انتخاب شد (در حالت فوکوس)", "description": "Overlay appearance select option for showing the field on focus of the input element" }, "autofillOverlayVisibilityOnButtonClick": { - "message": "When autofill icon is selected", + "message": "وقتی آیکون پر کردن خودکار انتخاب شود", "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Autofill on page load" + "message": "پر کردن خودکار هنگام بارگذاری صفحه" }, "enableAutoFillOnPageLoad": { "message": "پر کردن خودکار هنگام بارگذاری صفحه" @@ -1663,7 +1663,7 @@ "message": "وب‌سایت‌های در معرض خطر یا نامعتبر می‌توانند از پر کردن خودکار در بارگذاری صفحه سوء استفاده کنند." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "درباره‌ خطراتش بیش‌تر بدانید" }, "learnMoreAboutAutofill": { "message": "درباره پر کردن خودکار بیشتر بدانید" @@ -1693,13 +1693,13 @@ "message": "باز کردن گاوصندوق در نوار کناری" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "آخرین ورودی مورد استفاده برای وب سایت فعلی را به صورت خودکار پر کنید" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "آخرین کارت مورد استفاده برای وب سایت فعلی را به صورت خودکار پر کنید" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "آخرین هویت مورد استفاده برای وب سایت فعلی را به صورت خودکار پر کنید" }, "commandGeneratePasswordDesc": { "message": "یک کلمه عبور تصادفی جدید ایجاد کنید و آن را در کلیپ بورد کپی کنید" @@ -1723,7 +1723,7 @@ "message": "برای مرتب‌سازی بکشید" }, "dragToReorder": { - "message": "Drag to reorder" + "message": "برای تغییر ترتیب، بکشید و رها کنید" }, "cfTypeText": { "message": "متن" @@ -1735,7 +1735,7 @@ "message": "منطقی" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "کادر انتخاب" }, "cfTypeLinked": { "message": "پیوند شده", @@ -1758,7 +1758,7 @@ "message": "یک تصویر قابل تشخیص در کنار هر ورود نشان دهید." }, "faviconDescAlt": { - "message": "Show a recognizable image next to each login. Applies to all logged in accounts." + "message": "نمایش تصویر قابل تشخیص کنار هر ورود به سیستم. برای تمام حساب‌های کاربری وارد شده اعمال می‌شود." }, "enableBadgeCounter": { "message": "نمایش شمارنده نشان" @@ -1920,7 +1920,7 @@ "message": "هویت" }, "typeSshKey": { - "message": "SSH key" + "message": "کلید SSH" }, "newItemHeader": { "message": "$TYPE$ جدید", @@ -1941,7 +1941,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "مشاهده $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1953,13 +1953,13 @@ "message": "تاریخچه کلمه عبور" }, "generatorHistory": { - "message": "Generator history" + "message": "تاریخچه تولید کننده" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "پاک کردن تاریخچه تولید کننده" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "اگر ادامه دهید، تمام ورودی‌ها به‌طور دائمی از تاریخچه تولید کننده حذف خواهند شد. آیا مطمئن هستید که می‌خواهید ادامه دهید؟" }, "back": { "message": "بازگشت" @@ -1968,7 +1968,7 @@ "message": "مجموعه‌ها" }, "nCollections": { - "message": "$COUNT$ collections", + "message": "$COUNT$ مجموعه‌ها", "placeholders": { "count": { "content": "$1", @@ -1998,7 +1998,7 @@ "message": "یادداشت‌های امن" }, "sshKeys": { - "message": "SSH Keys" + "message": "کلید‌‌های SSH" }, "clear": { "message": "پاک کردن", @@ -2024,7 +2024,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "دامنه پایه (پیشنهادی)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -2078,13 +2078,13 @@ "message": "هیچ کلمه عبوری برای فهرست کردن وجود ندارد." }, "clearHistory": { - "message": "Clear history" + "message": "پاک کردن تاریخچه" }, "nothingToShow": { - "message": "Nothing to show" + "message": "چیزی برای نشان دادن موجود نیست" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "شما اخیراً چیزی تولید نکرده‌اید" }, "remove": { "message": "حذف" @@ -2145,16 +2145,16 @@ "message": "باز کردن با پین" }, "setYourPinTitle": { - "message": "Set PIN" + "message": "تنظیم کد پین" }, "setYourPinButton": { - "message": "Set PIN" + "message": "تنظیم کد پین" }, "setYourPinCode": { "message": "کد پین خود را برای باز کردن Bitwarden تنظیم کنید. اگر به طور کامل از برنامه خارج شوید، تنظیمات پین شما از بین می‌رود." }, "setYourPinCode1": { - "message": "Your PIN will be used to unlock Bitwarden instead of your master password. Your PIN will reset if you ever fully log out of Bitwarden." + "message": "کد پین شما برای باز کردن Bitwarden به جای کلمه عبور اصلی استفاده خواهد شد. در صورتی که کاملاً از Bitwarden خارج شوید، کد پین شما ریست خواهد شد." }, "pinRequired": { "message": "کد پین الزامیست." @@ -2163,13 +2163,13 @@ "message": "کد پین معتبر نیست." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "تعداد تلاش‌های ناموفق پین زیاد شد. خارج می‌شوید." }, "unlockWithBiometrics": { "message": "با استفاده از بیومتریک باز کنید" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "باز کردن قفل با کلمه عبور اصلی" }, "awaitDesktop": { "message": "در انتظار تأیید از دسکتاپ" @@ -2181,7 +2181,7 @@ "message": "در زمان شروع مجدد، با کلمه عبور اصلی قفل کن" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "در زمان شروع مجدد، کلمه عبور اصلی نیاز است" }, "selectOneCollection": { "message": "شما باید حداقل یک مجموعه را انتخاب کنید." @@ -2193,39 +2193,39 @@ "message": "شبیه سازی" }, "passwordGenerator": { - "message": "Password generator" + "message": "تولید کننده کلمه عبور" }, "usernameGenerator": { - "message": "Username generator" + "message": "تولید کننده نام کاربری" }, "useThisEmail": { - "message": "Use this email" + "message": "از این ایمیل استفاده شود" }, "useThisPassword": { - "message": "Use this password" + "message": "از این کلمه عبور استفاده کن" }, "useThisUsername": { - "message": "Use this username" + "message": "از این نام کاربری استفاده کن" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "کلمه عبور ایمن ساخته شد! فراموش نکنید کلمه عبور خود را در وب‌سایت نیز به‌روزرسانی کنید." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "از تولید کننده استفاده کنید", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "برای ایجاد یک کلمه عبور قوی و منحصر به فرد", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "vaultCustomization": { - "message": "Vault customization" + "message": "سفارشی‌سازی گاوصندوق" }, "vaultTimeoutAction": { "message": "عمل متوقف شدن گاو‌صندوق" }, "vaultTimeoutAction1": { - "message": "Timeout action" + "message": "اقدام در صورت پایان زمان" }, "lock": { "message": "قفل", @@ -2254,7 +2254,7 @@ "message": "مورد بازیابی شد" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "در حال حاضر حساب کاربری دارید؟" }, "vaultTimeoutLogOutConfirmation": { "message": "خروج از سیستم، تمام دسترسی ها به گاو‌صندوق شما را از بین می‌برد و نیاز به احراز هویت آنلاین پس از مدت زمان توقف دارد. آیا مطمئن هستید که می‌خواهید از این تنظیمات استفاده کنید؟" @@ -2347,7 +2347,7 @@ "message": "کلمه عبور اصلی جدید شما از شرایط سیاست پیروی نمی‌کند." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "پیشنهادها، اعلان‌ها و فرصت‌های تحقیقاتی Bitwarden را در صندوق ورودی خود دریافت کنید." }, "unsubscribe": { "message": "لغو اشتراک" @@ -2374,7 +2374,7 @@ "message": "سیاست حفظ حریم خصوصی" }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "کلمه عبور جدید شما نمی‌تواند با کلمه عبور فعلی‌تان یکسان باشد." }, "hintEqualsPassword": { "message": "اشاره به کلمه عبور شما نمی‌تواند همان کلمه عبور شما باشد." @@ -2383,10 +2383,10 @@ "message": "تأیید" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "خطای به‌روزرسانی توکن دسترسی" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "هیچ توکن به‌روزرسانی یا کلید API یافت نشد. لطفاً از حساب کاربری خود خارج شده و دوباره وارد شوید." }, "desktopSyncVerificationTitle": { "message": "تأیید همگام‌سازی دسکتاپ" @@ -2425,10 +2425,10 @@ "message": "عدم مطابقت حساب کاربری" }, "nativeMessagingWrongUserKeyTitle": { - "message": "Biometric key missmatch" + "message": "عدم تطابق کلید بیومتریک" }, "nativeMessagingWrongUserKeyDesc": { - "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + "message": "باز کردن قفل بیومتریک ناموفق بود. کلید مخفی بیومتریک نتوانست گاوصندوق را باز کند. لطفاً دوباره تنظیمات بیومتریک را انجام دهید." }, "biometricsNotEnabledTitle": { "message": "بیومتریک برپا نشده" @@ -2446,13 +2446,13 @@ "message": "کاربر قفل یا خارج شد" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "لطفاً این کاربر را در برنامه دسکتاپ باز کنید و دوباره تلاش کنید." }, "biometricsNotAvailableTitle": { - "message": "Biometric unlock unavailable" + "message": "باز کردن قفل بیومتریک در دسترس نیست" }, "biometricsNotAvailableDesc": { - "message": "Biometric unlock is currently unavailable. Please try again later." + "message": "باز کردن قفل بیومتریک در حال حاضر در دسترس نیست. لطفاً بعداً دوباره تلاش کنید." }, "biometricsFailedTitle": { "message": "زیست‌سنجی ناموفق بود" @@ -2479,17 +2479,17 @@ "message": "سیاست سازمانی بر تنظیمات مالکیت شما تأثیر می‌گذارد." }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "یک سیاست سازمانی، درون ریزی موارد به گاوصندوق فردی شما را مسدود کرده است." }, "domainsTitle": { - "message": "Domains", + "message": "دامنه‌ها", "description": "A category title describing the concept of web domains" }, "blockedDomains": { - "message": "Blocked domains" + "message": "دامنه‌های مسدود شده" }, "learnMoreAboutBlockedDomains": { - "message": "Learn more about blocked domains" + "message": "اطلاعات بیشتر درباره دامنه‌های مسدود شده" }, "excludedDomains": { "message": "دامنه های مستثنی" @@ -2498,22 +2498,22 @@ "message": "Bitwarden برای ذخیره جزئیات ورود به سیستم این دامنه ها سوال نمی‌کند. برای اینکه تغییرات اعمال شود باید صفحه را تازه کنید." }, "excludedDomainsDescAlt": { - "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." + "message": "Bitwarden برای هیچ یک از حساب‌های کاربری وارد شده، درخواست ذخیره اطلاعات ورود برای این دامنه‌ها را نخواهد داد. برای اعمال تغییرات باید صفحه را تازه‌سازی کنید." }, "blockedDomainsDesc": { - "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + "message": "ویژگی‌های پر کردن خودکار و سایر قابلیت‌های مرتبط برای این وب‌سایت‌ها ارائه نخواهند شد. برای اعمال تغییرات باید صفحه را تازه‌سازی کنید." }, "autofillBlockedNoticeV2": { - "message": "Autofill is blocked for this website." + "message": "پر کردن خودکار برای این وب‌سایت مسدود شده است." }, "autofillBlockedNoticeGuidance": { - "message": "Change this in settings" + "message": "این مورد را در تنظیمات تغییر دهید" }, "change": { - "message": "Change" + "message": "تغییر" }, "changeButtonTitle": { - "message": "Change password - $ITEMNAME$", + "message": "تغییر کلمه عبور - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -2522,10 +2522,10 @@ } }, "atRiskPasswords": { - "message": "At-risk passwords" + "message": "کلمات عبور در معرض خطر" }, "atRiskPasswordDescSingleOrg": { - "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "message": "$ORGANIZATION$ از شما درخواست کرده یک کلمه عبور را به دلیل در معرض خطر بودن تغییر دهید.", "placeholders": { "organization": { "content": "$1", @@ -2534,7 +2534,7 @@ } }, "atRiskPasswordsDescSingleOrgPlural": { - "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "message": "$ORGANIZATION$ از شما درخواست کرده $COUNT$ کلمه عبور را به دلیل در معرض خطر بودن تغییر دهید.", "placeholders": { "organization": { "content": "$1", @@ -2547,7 +2547,7 @@ } }, "atRiskPasswordsDescMultiOrgPlural": { - "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "message": "سازمان‌های شما از شما درخواست کرده‌اند که $COUNT$ کلمه عبور را به دلیل در معرض خطر بودن تغییر دهید.", "placeholders": { "count": { "content": "$1", @@ -2556,10 +2556,10 @@ } }, "reviewAndChangeAtRiskPassword": { - "message": "Review and change one at-risk password" + "message": "بررسی و تغییر یک کلمه عبور در معرض خطر" }, "reviewAndChangeAtRiskPasswordsPlural": { - "message": "Review and change $COUNT$ at-risk passwords", + "message": "بررسی و تغییر $COUNT$ کلمه عبور در معرض خطر", "placeholders": { "count": { "content": "$1", @@ -2568,52 +2568,52 @@ } }, "changeAtRiskPasswordsFaster": { - "message": "Change at-risk passwords faster" + "message": "تغییر سریع‌تر کلمات عبور در معرض خطر" }, "changeAtRiskPasswordsFasterDesc": { - "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + "message": "تنظیمات خود را به‌روزرسانی کنید تا بتوانید به‌سرعت کلمات عبور خود را به‌صورت خودکار وارد کرده و کلمات جدید تولید کنید" }, "reviewAtRiskLogins": { - "message": "Review at-risk logins" + "message": "بررسی ورودهای در معرض خطر" }, "reviewAtRiskPasswords": { - "message": "Review at-risk passwords" + "message": "بررسی کلمات عبور در معرض خطر" }, "reviewAtRiskLoginsSlideDesc": { - "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "message": "کلمات عبور سازمان شما در معرض خطر هستند زیرا ضعیف، تکراری و یا افشا شده‌اند.", "description": "Description of the review at-risk login slide on the at-risk password page carousel" }, "reviewAtRiskLoginSlideImgAltPeriod": { - "message": "Illustration of a list of logins that are at-risk." + "message": "تصویری از فهرست ورودهایی که در معرض خطر هستند." }, "generatePasswordSlideDesc": { - "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "message": "با استفاده از منوی پر کردن خودکار Bitwarden در سایت در معرض خطر، به‌سرعت یک کلمه عبور قوی و منحصر به فرد تولید کنید.", "description": "Description of the generate password slide on the at-risk password page carousel" }, "generatePasswordSlideImgAltPeriod": { - "message": "Illustration of the Bitwarden autofill menu displaying a generated password." + "message": "تصویری از منوی پر کردن خودکار Bitwarden که یک کلمه عبور تولید شده را نمایش می‌دهد." }, "updateInBitwarden": { - "message": "Update in Bitwarden" + "message": "به‌روزرسانی در Bitwarden" }, "updateInBitwardenSlideDesc": { - "message": "Bitwarden will then prompt you to update the password in the password manager.", + "message": "سپس Bitwarden از شما می‌خواهد کلمه عبور را در مدیریت کلمه عبور به‌روزرسانی کنید.", "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" }, "updateInBitwardenSlideImgAltPeriod": { - "message": "Illustration of a Bitwarden’s notification prompting the user to update the login." + "message": "تصویری از اعلان Bitwarden که از کاربر می‌خواهد ورود خود را به‌روزرسانی کند." }, "turnOnAutofill": { - "message": "Turn on autofill" + "message": "پر کردن خودکار را فعال کنید" }, "turnedOnAutofill": { - "message": "Turned on autofill" + "message": "پر کردن خودکار فعال شد" }, "dismiss": { - "message": "Dismiss" + "message": "نادیده گرفتن" }, "websiteItemLabel": { - "message": "Website $number$ (URI)", + "message": "وب‌سایت $number$ (نشانی اینترنتی)", "placeholders": { "number": { "content": "$1", @@ -2631,20 +2631,20 @@ } }, "blockedDomainsSavedSuccess": { - "message": "Blocked domain changes saved" + "message": "تغییرات دامنه مسدود شده ذخیره شد" }, "excludedDomainsSavedSuccess": { - "message": "Excluded domain changes saved" + "message": "تغییرات دامنه مستثنی شده ذخیره شد" }, "limitSendViews": { - "message": "Limit views" + "message": "محدود کردن نمایش‌ها" }, "limitSendViewsHint": { - "message": "No one can view this Send after the limit is reached.", + "message": "هیچ‌کس نمی‌تواند این را پس از رسیدن به محدودیت مشاهده کند.", "description": "Displayed under the limit views field on Send" }, "limitSendViewsCount": { - "message": "$ACCESSCOUNT$ views left", + "message": "$ACCESSCOUNT$ بازدید باقی مانده", "description": "Displayed under the limit views field on Send", "placeholders": { "accessCount": { @@ -2658,14 +2658,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Send details", + "message": "جزئیات ارسال", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { "message": "متن" }, "sendTypeTextToShare": { - "message": "Text to share" + "message": "متن برای اشتراک گذاری" }, "sendTypeFile": { "message": "پرونده" @@ -2675,7 +2675,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "hideTextByDefault": { - "message": "Hide text by default" + "message": "متن را به‌صورت پیش‌فرض مخفی کن" }, "expired": { "message": "منقضی شده" @@ -2684,7 +2684,7 @@ "message": "محافظت ‌شده با کلمه عبور" }, "copyLink": { - "message": "Copy link" + "message": "کپی پیوند" }, "copySendLink": { "message": "پیوند ارسال را کپی کن", @@ -2722,7 +2722,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendPermanentConfirmation": { - "message": "Are you sure you want to permanently delete this Send?", + "message": "مطمئن هستید می‌خواهید این ارسال را برای همیشه پاک کنید؟", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -2733,7 +2733,7 @@ "message": "تاریخ حذف" }, "deletionDateDescV2": { - "message": "The Send will be permanently deleted on this date.", + "message": "ارسال در این تاریخ به‌طور دائمی حذف خواهد شد.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -2755,7 +2755,7 @@ "message": "سفارشی" }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", + "message": "یک کلمه عبور اختیاری برای دریافت‌کنندگان اضافه کنید تا بتوانند به این ارسال دسترسی داشته باشند.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { @@ -2778,15 +2778,15 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Send created successfully!", + "message": "ارسال با موفقیت ساخته شد!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHoursSingle": { - "message": "The Send will be available to anyone with the link for the next 1 hour.", + "message": "این ارسال به مدت ۱ ساعت برای هر کسی که لینک را دارد در دسترس خواهد بود.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHours": { - "message": "The Send will be available to anyone with the link for the next $HOURS$ hours.", + "message": "این ارسال به مدت $HOURS$ ساعت برای هر کسی که لینک را دارد در دسترس خواهد بود.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "hours": { @@ -2796,11 +2796,11 @@ } }, "sendExpiresInDaysSingle": { - "message": "The Send will be available to anyone with the link for the next 1 day.", + "message": "این ارسال به مدت ۱ روز برای هر کسی که لینک را دارد در دسترس خواهد بود.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInDays": { - "message": "The Send will be available to anyone with the link for the next $DAYS$ days.", + "message": "این ارسال به مدت $DAYS$ روز برای هر کسی که لینک را دارد در دسترس خواهد بود.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2810,7 +2810,7 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "لینک ارسال کپی شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { @@ -2818,11 +2818,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogText": { - "message": "Pop out extension?", + "message": "باز کردن پنجره جداگانه افزونه؟", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogDesc": { - "message": "To create a file Send, you need to pop out the extension to a new window.", + "message": "برای ایجاد یک ارسال پرونده، باید افزونه را در پنجره‌ای جدید باز کنید.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinuxChromiumFileWarning": { @@ -2835,7 +2835,7 @@ "message": "برای انتخاب پرونده ای با استفاده از Safari، با کلیک روی این بنر پنجره جدیدی باز کنید." }, "popOut": { - "message": "Pop out" + "message": "باز کردن در پنجره جداگانه" }, "sendFileCalloutHeader": { "message": "قبل از اینکه شروع کنی" @@ -2856,7 +2856,7 @@ "message": "هنگام ذخیره حذف و تاریخ انقضاء شما خطایی روی داد." }, "hideYourEmail": { - "message": "Hide your email address from viewers." + "message": "آدرس ایمیل خود را از بینندگان مخفی کنید." }, "passwordPrompt": { "message": "درخواست مجدد کلمه عبور اصلی" @@ -2871,7 +2871,7 @@ "message": "تأیید ایمیل لازم است" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "ایمیل تأیید شد" }, "emailVerificationRequiredDesc": { "message": "برای استفاده از این ویژگی باید ایمیل خود را تأیید کنید. می‌توانید ایمیل خود را در گاوصندوق وب تأیید کنید." @@ -2889,7 +2889,7 @@ "message": "کلمه عبور اصلی شما با یک یا چند سیاست سازمان‌تان مطابقت ندارد. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را به‌روز کنید. در صورت ادامه، شما از نشست فعلی خود خارج می‌شوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "سازمان شما رمزگذاری دستگاه‌های مورد اعتماد را غیرفعال کرده است. لطفاً برای دسترسی به گاوصندوق خود یک کلمه عبور اصلی تنظیم کنید." }, "resetPasswordPolicyAutoEnroll": { "message": "ثبت نام خودکار" @@ -2905,15 +2905,15 @@ "description": "Used as a message within the notification bar when no folders are found" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "Your organization permissions were updated, requiring you to set a master password.", + "message": "مجوزهای سازمان شما به‌روزرسانی شد، باید یک کلمه عبور اصلی تنظیم کنید.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Your organization requires you to set a master password.", + "message": "سازمانتان از شما می‌خواهد که یک کلمه عبور اصلی تنظیم کنید.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "از میان $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2932,7 +2932,7 @@ "message": "دقیقه" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "نیازمندی‌های سیاست سازمانی بر گزینه‌های زمان پایان نشست شما اعمال شده است" }, "vaultTimeoutPolicyInEffect": { "message": "سیاست‌های سازمانتان بر مهلت زمانی گاوصندوق شما تأثیر می‌گذارد. حداکثر زمان مجاز گاوصندوق $HOURS$ ساعت و $MINUTES$ دقیقه است", @@ -2948,7 +2948,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "حداکثر $HOURS$ ساعت و $MINUTES$ دقیقه.", "placeholders": { "hours": { "content": "$1", @@ -2961,7 +2961,7 @@ } }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "زمان پایان نشست بیشتر از محدودیتی است که سازمان شما تعیین کرده است: حداکثر $HOURS$ ساعت و $MINUTES$ دقیقه", "placeholders": { "hours": { "content": "$1", @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "شناسه منحصر به فردی یافت نشد." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ در حال استفاده از SSO با یک سرور کلید خود میزبان است. برای ورود اعضای این سازمان دیگر نیازی به کلمه عبور اصلی نیست.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "برای اعضای سازمان زیر، کلمه عبور اصلی دیگر لازم نیست. لطفاً دامنه زیر را با مدیر سازمان خود تأیید کنید." + }, + "organizationName": { + "message": "نام سازمان" + }, + "keyConnectorDomain": { + "message": "دامنه رابط کلید" }, "leaveOrganization": { "message": "ترک سازمان" @@ -3057,7 +3057,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included", + "message": "فقط موردهای فردی گاوصندوق شامل پیوست‌ها که به $EMAIL$ مرتبط هستند برون ریزی خواهند شد. موردهای گاوصندوق سازمانی شامل نمی‌شوند", "placeholders": { "email": { "content": "$1", @@ -3066,10 +3066,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "در حال برون ریزی گاوصندوق سازمان" }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "فقط گاوصدوق سازمان مرتبط با $ORGANIZATION$ برون ریزی خواهد شد. موارد موجود در گاوصندوق‌های فردی یا سایر سازمان‌ها شامل نمی‌شوند.", "placeholders": { "organization": { "content": "$1", @@ -3081,27 +3081,27 @@ "message": "خطا" }, "decryptionError": { - "message": "Decryption error" + "message": "خطای رمزگشایی" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden could not decrypt the vault item(s) listed below." + "message": "Bitwarden نتوانست مورد(های) گاوصندوق فهرست شده زیر را رمزگشایی کند." }, "contactCSToAvoidDataLossPart1": { - "message": "Contact customer success", + "message": "با بخش پشتیبانی مشتریان تماس بگیرید", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { - "message": "to avoid additional data loss.", + "message": "برای جلوگیری از، از دست دادن داده‌های بیشتر.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "ایجاد نام کاربری" }, "generateEmail": { - "message": "Generate email" + "message": "تولید ایمیل" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "مقدار باید بین $MIN$ و $MAX$ باشد.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -3115,7 +3115,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " برای تولید یک کلمه عبور قوی، از $RECOMMENDED$ کاراکتر یا بیشتر استفاده کنید.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -3125,7 +3125,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " برای تولید یک عبارت عبور قوی، از $RECOMMENDED$ واژه یا بیشتر استفاده کنید.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -3166,15 +3166,15 @@ "message": "یک نام مستعار ایمیل با یک سرویس ارسال خارجی ایجاد کنید." }, "forwarderDomainName": { - "message": "Email domain", + "message": "دامنه ایمیل", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "دامنه‌ای را انتخاب کنید که توسط سرویس انتخاب شده پشتیبانی می‌شود", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ خطا: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -3188,11 +3188,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "تولید شده توسط Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "وب‌سایت: $WEBSITE$. تولید شده توسط Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -3202,7 +3202,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "توکن API نامعتبر برای $SERVICENAME$", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -3212,7 +3212,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "توکن API نامعتبر برای $SERVICENAME$: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -3226,7 +3226,7 @@ } }, "forwaderInvalidOperation": { - "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "message": "$SERVICENAME$ درخواست شما را رد کرد. لطفاً برای دریافت کمک با ارائه‌دهنده سرویس خود تماس بگیرید.", "description": "Displayed when the user is forbidden from using the API by the forwarding service.", "placeholders": { "servicename": { @@ -3236,7 +3236,7 @@ } }, "forwaderInvalidOperationWithMessage": { - "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "message": "$SERVICENAME$ درخواست شما را رد کرد: $ERRORMESSAGE$", "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -3250,7 +3250,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "دریافت شناسه حساب ایمیل ماسک شده از $SERVICENAME$ امکان‌پذیر نیست.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -3290,7 +3290,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "فرواردکننده ناشناخته: $SERVICENAME$.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -3379,34 +3379,34 @@ "message": "ارسال مجدد اعلان" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "مشاهده همه گزینه‌های ورود به سیستم" }, "notificationSentDevice": { "message": "یک اعلان به دستگاه شما ارسال شده است." }, "notificationSentDevicePart1": { - "message": "Unlock Bitwarden on your device or on the" + "message": "قفل Bitwarden را در دستگاه خود یا در... باز کنید" }, "notificationSentDeviceAnchor": { - "message": "web app" + "message": "برنامه وب" }, "notificationSentDevicePart2": { - "message": "Make sure the Fingerprint phrase matches the one below before approving." + "message": "اطمینان حاصل کنید که عبارت اثر انگشت با عبارت زیر مطابقت دارد قبل از تأیید." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "یک اعلان به دستگاه شما ارسال شده است" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "به‌محض تأیید درخواست، به شما اطلاع داده خواهد شد" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "به گزینه دیگری نیاز دارید؟" }, "loginInitiated": { "message": "ورود به سیستم آغاز شد" }, "logInRequestSent": { - "message": "Request sent" + "message": "درخواست ارسال شد" }, "exposedMasterPassword": { "message": "کلمه عبور اصلی افشا شده" @@ -3445,7 +3445,7 @@ "message": "نحوه پر کردن خودکار" }, "autofillSelectInfoWithCommand": { - "message": "Select an item from this screen, use the shortcut $COMMAND$, or explore other options in settings.", + "message": "یک مورد را از این صفحه انتخاب کنید، از میان‌بر $COMMAND$ استفاده کنید، یا گزینه‌های دیگر را در تنظیمات بررسی کنید.", "placeholders": { "command": { "content": "$1", @@ -3454,7 +3454,7 @@ } }, "autofillSelectInfoWithoutCommand": { - "message": "Select an item from this screen, or explore other options in settings." + "message": "یک مورد را از این صفحه انتخاب کنید یا گزینه‌های دیگر را در تنظیمات بررسی کنید." }, "gotIt": { "message": "متوجه شدم" @@ -3463,22 +3463,22 @@ "message": "تنظیمات پر کردن خودکار" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Autofill shortcut" + "message": "میان‌بر پرکردن خودکار" }, "autofillKeyboardShortcutUpdateLabel": { - "message": "Change shortcut" + "message": "تغییر میان‌بر" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "مدیریت میان‌برها" }, "autofillShortcut": { "message": "میانبر صفحه کلید پر کردن خودکار" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "میان‌بر ورود خودکار تنظیم نشده است. این مورد را در تنظیمات مرورگر تغییر دهید." }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "میان‌بر ورود خودکار $COMMAND$ است. همه میان‌برها را می‌توانید در تنظیمات مرورگر مدیریت کنید.", "placeholders": { "command": { "content": "$1", @@ -3499,16 +3499,16 @@ "message": "در پنجره جدید باز می‌شود" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "این دستگاه را به خاطر بسپار تا ورودهای بعدی بدون مشکل انجام شود" }, "deviceApprovalRequired": { "message": "تأیید دستگاه لازم است. یک روش تأیید انتخاب کنید:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "تأیید دستگاه لازم است" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "یکی از گزینه‌های تأیید زیر را انتخاب کنید" }, "rememberThisDevice": { "message": "این دستگاه را به خاطر بسپار" @@ -3538,13 +3538,13 @@ "message": "و به ساختن حساب‌تان ادامه دهید." }, "noEmail": { - "message": "No email?" + "message": "ایمیلی دریافت نکردید؟" }, "goBack": { - "message": "Go back" + "message": "بازگشت به عقب" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "برای ویرایش آدرس ایمیل خود." }, "eu": { "message": "اروپا", @@ -3578,41 +3578,49 @@ "message": "ایمیل کاربر وجود ندارد" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "ایمیل کاربر فعال پیدا نشد. در حال خارج کردن شما از سیستم هستیم." }, "deviceTrusted": { "message": "دستگاه مورد اعتماد است" }, "trustOrganization": { - "message": "Trust organization" + "message": "اعتماد به سازمان" }, "trust": { - "message": "Trust" + "message": "اطمینان" }, "doNotTrust": { - "message": "Do not trust" + "message": "اعتماد نکنید" }, "organizationNotTrusted": { - "message": "Organization is not trusted" + "message": "سازمان مورد اعتماد نیست" }, "emergencyAccessTrustWarning": { - "message": "For the security of your account, only confirm if you have granted emergency access to this user and their fingerprint matches what is displayed in their account" + "message": "برای امنیت حساب کاربری شما، فقط در صورتی تأیید کنید که دسترسی اضطراری به این کاربر داده‌اید و اثر انگشت او با آنچه در حسابش نمایش داده شده، مطابقت دارد" }, "orgTrustWarning": { - "message": "For the security of your account, only proceed if you are a member of this organization, have account recovery enabled, and the fingerprint displayed below matches the organization's fingerprint." + "message": "برای امنیت حساب کاربری شما، فقط در صورتی ادامه دهید که عضو این سازمان باشید، بازیابی حساب کاربری را فعال کرده باشید و اثر انگشت نمایش داده شده در زیر با اثر انگشت سازمان مطابقت داشته باشد." }, "orgTrustWarning1": { - "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + "message": "این سازمان دارای سیاست سازمانی است که شما را در بازیابی حساب کاربری ثبت‌نام می‌کند. ثبت‌نام به مدیران سازمان اجازه می‌دهد کلمه عبور شما را تغییر دهند. فقط در صورتی ادامه دهید که این سازمان را می‌شناسید و عبارت اثر انگشت نمایش داده شده در زیر با اثر انگشت سازمان مطابقت دارد." }, "trustUser": { - "message": "Trust user" + "message": "به کاربر اعتماد کنید" }, "sendsNoItemsTitle": { - "message": "No active Sends", + "message": "ارسال‌های فعالی نیست", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "message": "از ارسال برای اشتراک‌گذاری امن اطلاعات رمزگذاری شده با هر کسی استفاده کنید.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsTitleNoItems": { + "message": "اطلاعات حساس را به‌صورت ایمن ارسال کنید", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "پرونده‌ها و داده‌های خود را به‌صورت امن با هر کسی، در هر پلتفرمی به اشتراک بگذارید. اطلاعات شما در حین اشتراک‌گذاری به‌طور کامل رمزگذاری انتها به انتها باقی خواهد ماند و میزان افشا محدود می‌شود.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3689,10 +3697,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "۱ فیلد به توجه شما نیاز دارد." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "فیلدهای $COUNT$ به توجه شما نیاز دارند.", "placeholders": { "count": { "content": "$1", @@ -3747,53 +3755,53 @@ "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "تغییر وضعیت ناوبری کناری" }, "skipToContent": { - "message": "Skip to content" + "message": "پرش به محتوا" }, "bitwardenOverlayButton": { - "message": "Bitwarden autofill menu button", + "message": "دکمه منوی پر کردن خودکار Bitwarden", "description": "Page title for the iframe containing the overlay button" }, "toggleBitwardenVaultOverlay": { - "message": "Toggle Bitwarden autofill menu", + "message": "نمایش یا مخفی کردن منوی پر کردن خودکار Bitwarden", "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { - "message": "Bitwarden autofill menu", + "message": "منوی پر کردن خودکار Bitwarden", "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { - "message": "Unlock your account to view matching logins", + "message": "برای مشاهده ورودهای منطبق، قفل حساب کاربری خود را باز کنید", "description": "Text to display in overlay when the account is locked." }, "unlockYourAccountToViewAutofillSuggestions": { - "message": "Unlock your account to view autofill suggestions", + "message": "برای مشاهده پیشنهادهای پر کردن خودکار، قفل حساب کاربری خود را باز کنید", "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { - "message": "Unlock account", + "message": "قفل حساب کاربری را باز کنید", "description": "Button text to display in overlay when the account is locked." }, "unlockAccountAria": { - "message": "Unlock your account, opens in a new window", + "message": "قفل حساب کاربری خود را باز کنید، در پنجره‌ای جدید باز می‌شود", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "totpCodeAria": { - "message": "Time-based One-Time Password Verification Code", + "message": "کد تأیید رمز یک‌بار مصرف مبتنی بر زمان", "description": "Aria label for the totp code displayed in the inline menu for autofill" }, "totpSecondsSpanAria": { - "message": "Time remaining before current TOTP expires", + "message": "زمان باقی‌مانده تا انقضای کد TOTP فعلی", "description": "Aria label for the totp seconds displayed in the inline menu for autofill" }, "fillCredentialsFor": { - "message": "Fill credentials for", + "message": "پر کردن اطلاعات ورود برای", "description": "Screen reader text for when overlay item is in focused" }, "partialUsername": { - "message": "Partial username", + "message": "نام کاربری جزئی", "description": "Screen reader text for when a login item is focused where a partial username is displayed. SR will announce this phrase before reading the text of the partial username" }, "noItemsToShow": { @@ -3809,31 +3817,31 @@ "description": "Screen reader text (aria-label) for new item button in overlay" }, "newLogin": { - "message": "New login", + "message": "ورود جدید", "description": "Button text to display within inline menu when there are no matching items on a login field" }, "addNewLoginItemAria": { - "message": "Add new vault login item, opens in a new window", + "message": "افزودن مورد ورود جدید به گاوصندوق، در پنجره‌ای جدید باز می‌شود", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "کارت جدید", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { - "message": "Add new vault card item, opens in a new window", + "message": "افزودن کارت جدید به گاوصندوق، در پنجره‌ای جدید باز می‌شود", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "New identity", + "message": "هویت جدید", "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { - "message": "Add new vault identity item, opens in a new window", + "message": "افزودن هویت جدید به گاوصندوق، در پنجره‌ای جدید باز می‌شود", "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { - "message": "Bitwarden autofill menu available. Press the down arrow key to select.", + "message": "منوی پر کردن خودکار Bitwarden در دسترس است. برای انتخاب، کلید فلش پایین را فشار دهید.", "description": "Screen reader text for announcing when the overlay opens on the page" }, "turnOn": { @@ -3847,13 +3855,13 @@ "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" }, "importError": { - "message": "خطای وارد کردن" + "message": "خطای درون ریزی" }, "importErrorDesc": { "message": "مشکلی با داده‌هایی که سعی کردید وارد کنید وجود داشت. لطفاً خطاهای فهرست شده زیر را در فایل منبع خود برطرف کرده و دوباره امتحان کنید." }, "resolveTheErrorsBelowAndTryAgain": { - "message": "Resolve the errors below and try again." + "message": "خطاهای زیر را برطرف کرده و دوباره امتحان کنید." }, "description": { "message": "توضیحات" @@ -3874,10 +3882,10 @@ "message": "دوباره سعی کنید" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "برای این اقدام تأیید لازم است. یک کد پین تعیین کنید تا ادامه دهید." }, "setPin": { - "message": "تنظیم PIN" + "message": "تنظیم کد پین" }, "verifyWithBiometrics": { "message": "تایید با استفاده از بیومتریک" @@ -3892,25 +3900,25 @@ "message": "نیازمند روش دیگری هستید؟" }, "useMasterPassword": { - "message": "استفاده از رمز عبور اصلی" + "message": "استفاده از کلمه عبور اصلی" }, "usePin": { - "message": "استفاده از PIN" + "message": "استفاده از کد پین" }, "useBiometrics": { "message": "استفاده از بیومتریک" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "کد تأییدی را که به ایمیل شما ارسال شده است وارد کنید." }, "resendCode": { - "message": "Resend code" + "message": "ارسال دوباره کد" }, "total": { "message": "مجموع" }, "importWarning": { - "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "message": "شما در حال درون ریزی داده به $ORGANIZATION$ هستید. داده‌های شما ممکن است با اعضای این سازمان به اشتراک گذاشته شود. آیا می‌خواهید ادامه دهید؟", "placeholders": { "organization": { "content": "$1", @@ -3919,13 +3927,13 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "خطا در اتصال به سرویس Duo. از روش ورود دو مرحله‌ای دیگری استفاده کنید یا برای دریافت کمک با Duo تماس بگیرید." }, "duoRequiredForAccount": { - "message": "Duo two-step login is required for your account." + "message": "ورود دو مرحله ای Duo برای حساب کاربری شما لازم است." }, "popoutExtension": { - "message": "Popout extension" + "message": "باز کردن پنجره جداگانه افزونه" }, "launchDuo": { "message": "اجرای Duo" @@ -3937,25 +3945,25 @@ "message": "چیزی وارد نشد." }, "importEncKeyError": { - "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + "message": "خطا در رمزگشایی پرونده‌ی درون ریزی شده. کلید رمزگذاری شما با کلید رمزگذاری استفاده شده برای درون ریزی داده‌ها مطابقت ندارد." }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "کلمه عبور پرونده نامعتبر است، لطفاً از کلمه عبوری که هنگام ایجاد پرونده‌ی برون ریزی وارد کردید استفاده کنید." }, "destination": { - "message": "Destination" + "message": "مقصد" }, "learnAboutImportOptions": { - "message": "Learn about your import options" + "message": "درباره گزینه‌های برون ریزی خود بیاموزید" }, "selectImportFolder": { "message": "یک پوشه انتخاب کنید" }, "selectImportCollection": { - "message": "Select a collection" + "message": "انتخاب یک مجموعه" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "اگر می‌خواهید محتوای فایل وارد شده به $DESTINATION$ منتقل شود، این گزینه را انتخاب کنید", "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", "placeholders": { "destination": { @@ -3965,25 +3973,25 @@ } }, "importUnassignedItemsError": { - "message": "File contains unassigned items." + "message": "پرونده حاوی موارد اختصاص نیافته است." }, "selectFormat": { - "message": "Select the format of the import file" + "message": "فرمت پرونده‌ی درون ریزی را انتخاب کنید" }, "selectImportFile": { - "message": "Select the import file" + "message": "پرونده‌ی درون ریزی را انتخاب کنید" }, "chooseFile": { - "message": "Choose File" + "message": "انتخاب پرونده" }, "noFileChosen": { - "message": "No file chosen" + "message": "هیچ پرونده‌ای انتخاب نشد" }, "orCopyPasteFileContents": { - "message": "or copy/paste the import file contents" + "message": "یا محتویات پرونده‌ی درون ریزی را کپی/پیست کنید" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "دستورالعمل‌های $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -3993,25 +4001,25 @@ } }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "درون ریزی گاوصندوق را تأیید کنید" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "این پرونده با کلمه عبور محافظت شده است. لطفاً کلمه عبور پرونده را برای درون ریزی داده‌ها وارد کنید." }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "تأیید کلمه عبور پرونده" }, "exportSuccess": { - "message": "Vault data exported" + "message": "داده های گاوصندوق برون ریزی شد" }, "typePasskey": { - "message": "Passkey" + "message": "کلید عبور" }, "accessing": { - "message": "Accessing" + "message": "در حال دسترسی" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "وارد شده!" }, "passkeyNotCopied": { "message": "کلید عبور کپی نمی‌شود" @@ -4023,7 +4031,7 @@ "message": "تأیید توسط سایت آغازگر الزامی است. این ویژگی هنوز برای حساب‌های بدون کلمه عبور اصلی اجرا نشده است." }, "logInWithPasskeyQuestion": { - "message": "Log in with passkey?" + "message": "با کلید عبور وارد می‌شوید؟" }, "passkeyAlreadyExists": { "message": "یک کلید عبور از قبل برای این برنامه وجود دارد." @@ -4035,10 +4043,10 @@ "message": "شما هیچ ورود مشابهی برای این سایت ندارید." }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "ورود منطبق برای این سایت یافت نشد" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "جستجو یا ذخیره کلید عبور به عنوان ورود جدید" }, "confirm": { "message": "تأیید" @@ -4050,10 +4058,10 @@ "message": "کلید عبور را به عنوان ورود جدید ذخیره کن" }, "chooseCipherForPasskeySave": { - "message": "Choose a login to save this passkey to" + "message": "یک ورود برای ذخیره این کلید عبور انتخاب کنید" }, "chooseCipherForPasskeyAuth": { - "message": "Choose a passkey to log in with" + "message": "یک کلید عبور برای ورود انتخاب کنید" }, "passkeyItem": { "message": "مورد کلید عبور" @@ -4071,125 +4079,125 @@ "message": "برای استفاده از کلید عبور، احراز هویت لازم است. برای ادامه، هویت خود را تأیید کنید." }, "multifactorAuthenticationCancelled": { - "message": "Multifactor authentication cancelled" + "message": "تأیید هویت چند مرحله‌ای کنسل شد" }, "noLastPassDataFound": { - "message": "No LastPass data found" + "message": "هیچ داده‌ای از LastPass یافت نشد" }, "incorrectUsernameOrPassword": { - "message": "Incorrect username or password" + "message": "نام کاربری یا کلمه عبور اشتباه است" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "کلمه عبور اشتباه است" }, "incorrectCode": { - "message": "Incorrect code" + "message": "کد اشتباه است" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "کد پین نادرست است" }, "multifactorAuthenticationFailed": { - "message": "Multifactor authentication failed" + "message": "احراز هویت چند مرحله‌ای ناموفق بود" }, "includeSharedFolders": { - "message": "Include shared folders" + "message": "شامل پوشه‌های به‌ اشتراک گذاری‌ شده" }, "lastPassEmail": { - "message": "LastPass Email" + "message": "ایمیل LastPass" }, "importingYourAccount": { - "message": "Importing your account..." + "message": "در حال درون ریزی حساب کاربری شما..." }, "lastPassMFARequired": { - "message": "LastPass multifactor authentication required" + "message": "احراز هویت چند مرحله‌ای LastPass مورد نیاز است" }, "lastPassMFADesc": { - "message": "Enter your one-time passcode from your authentication app" + "message": "کد یک‌بار مصرف خود را از برنامه احراز هویت وارد کنید" }, "lastPassOOBDesc": { - "message": "Approve the login request in your authentication app or enter a one-time passcode." + "message": "درخواست ورود را در برنامه احراز هویت خود تأیید کنید یا یک کد یک‌بار مصرف وارد کنید." }, "passcode": { - "message": "Passcode" + "message": "کد عبور" }, "lastPassMasterPassword": { - "message": "LastPass master password" + "message": "کلمه عبور اصلی LastPass" }, "lastPassAuthRequired": { - "message": "LastPass authentication required" + "message": "احراز هویت LastPass مورد نیاز است" }, "awaitingSSO": { - "message": "Awaiting SSO authentication" + "message": "در انتظار احراز هویت SSO" }, "awaitingSSODesc": { - "message": "Please continue to log in using your company credentials." + "message": "لطفاً برای ورود، از اطلاعات کاربری شرکت خود استفاده کنید." }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "دستورالعمل‌های کامل را در سایت راهنمای ما مشاهده کنید در", "description": "This is followed a by a hyperlink to the help website." }, "importDirectlyFromLastPass": { - "message": "Import directly from LastPass" + "message": "درون ریزی مستقیم از LastPass" }, "importFromCSV": { - "message": "Import from CSV" + "message": "درون ریزی از CSV" }, "lastPassTryAgainCheckEmail": { - "message": "Try again or look for an email from LastPass to verify it's you." + "message": "دوباره تلاش کنید یا ایمیلی از LastPass را بررسی کنید تا هویت شما تأیید شود." }, "collection": { - "message": "Collection" + "message": "مجموعه" }, "lastPassYubikeyDesc": { - "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." + "message": "کلید YubiKey مربوط به حساب کاربری LastPass خود را در درگاه USB کامپیوتر قرار دهید، سپس دکمه آن را لمس کنید." }, "switchAccount": { - "message": "Switch account" + "message": "تعویض حساب کاربری" }, "switchAccounts": { - "message": "Switch accounts" + "message": "تعویض حساب‌ها" }, "switchToAccount": { - "message": "Switch to account" + "message": "تعویض به حساب کاربری" }, "activeAccount": { - "message": "Active account" + "message": "حساب کاربری فعال" }, "bitwardenAccount": { - "message": "Bitwarden account" + "message": "حساب کاربری Bitwarden" }, "availableAccounts": { - "message": "Available accounts" + "message": "حساب کاربری در درسترس" }, "accountLimitReached": { - "message": "Account limit reached. Log out of an account to add another." + "message": "محدودیت حساب کاربری تکمیل شد. برای افزودن حساب کاربری دیگر، از یک حساب خارج شوید." }, "active": { - "message": "active" + "message": "فعال" }, "locked": { - "message": "locked" + "message": "قفل شد" }, "unlocked": { - "message": "unlocked" + "message": "باز شد" }, "server": { - "message": "server" + "message": "سرور" }, "hostedAt": { - "message": "hosted at" + "message": "میزبانی شده در" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "از دستگاه یا کلید سخت‌افزاری خود استفاده کنید" }, "justOnce": { - "message": "Just once" + "message": "فقط یک بار" }, "alwaysForThisSite": { - "message": "Always for this site" + "message": "همیشه برای این سایت" }, "domainAddedToExcludedDomains": { - "message": "$DOMAIN$ added to excluded domains.", + "message": "دامنه $DOMAIN$ به دامنه‌های مستثنی اضافه شد.", "placeholders": { "domain": { "content": "$1", @@ -4198,106 +4206,106 @@ } }, "commonImportFormats": { - "message": "Common formats", + "message": "فرمت‌های رایج", "description": "Label indicating the most common import formats" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "ادامه به تنظیمات مرورگر؟", "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" }, "confirmContinueToHelpCenter": { - "message": "Continue to Help Center?", + "message": "به مرکز راهنمایی ادامه می‌دهید؟", "description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page" }, "confirmContinueToHelpCenterPasswordManagementContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "تنظیمات پر کردن خودکار و مدیریت کلمه عبور مرورگر خود را تغییر دهید.", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser password management settings" }, "confirmContinueToHelpCenterKeyboardShortcutsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "می‌توانید میان‌برهای افزونه را در تنظیمات مرورگر خود مشاهده و تنظیم کنید.", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser keyboard shortcut settings" }, "confirmContinueToBrowserPasswordManagementSettingsContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "تنظیمات پر کردن خودکار و مدیریت کلمه عبور مرورگر خود را تغییر دهید.", "description": "Body content for dialog which asks if the user wants to proceed to the browser's password management settings page" }, "confirmContinueToBrowserKeyboardShortcutSettingsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "می‌توانید میان‌برهای افزونه را در تنظیمات مرورگر خود مشاهده و تنظیم کنید.", "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { - "message": "Make Bitwarden your default password manager?", + "message": "می‌خواهید Bitwarden را مدیر کلمه عبور پیش‌فرض خود کنید؟", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { - "message": "Ignoring this option may cause conflicts between Bitwarden autofill suggestions and your browser's.", + "message": "نادیده گرفتن این گزینه ممکن است باعث تداخل بین پیشنهادهای پر کردن خودکار Bitwarden و مرورگر شما شود.", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "Make Bitwarden your default password manager", + "message": "Bitwarden را به عنوان مدیر کلمه عبور پیش‌فرض خود تنظیم کنید", "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { - "message": "Unable to set Bitwarden as the default password manager", + "message": "امکان تنظیم Bitwarden به‌عنوان مدیر کلمه عبور پیش‌فرض وجود ندارد", "description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "privacyPermissionAdditionNotGrantedDescription": { - "message": "You must grant browser privacy permissions to Bitwarden to set it as the default password manager.", + "message": "برای تنظیم Bitwarden به‌عنوان مدیر کلمه عبور پیش‌فرض، باید دسترسی‌های حفظ حریم خصوصی مرورگر را به آن بدهید.", "description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "makeDefault": { - "message": "Make default", + "message": "تنظیم به‌عنوان پیش‌فرض", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "اطلاعات ورود با موفقیت ذخیره شد!", "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "کلمه عبور ذخیره شد!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "اطلاعات ورود با موفقیت به‌روزرسانی شد!", "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "کلمه عبور به‌روزرسانی شد!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { - "message": "Error saving credentials. Check console for details.", + "message": "خطا در ذخیره اطلاعات ورود. جزئیات را در کنسول بررسی کنید.", "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "موفقیت آمیز بود" }, "removePasskey": { - "message": "Remove passkey" + "message": "حذف کلید عبور" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "کلید عبور حذف شد" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "پیشنهادهای پر کردن خودکار" }, "itemSuggestions": { - "message": "Suggested items" + "message": "موارد پیشنهادی" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to autofill" + "message": "یک مورد ورود برای این سایت ذخیره کنید تا به‌صورت خودکار پر شود" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "گاوصندوق‌تان خالی است" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "موردی با جستجوی شما مطابقت نداشت" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "فیلترها را پاک کنید یا عبارت جستجوی دیگری را امتحان کنید" }, "copyInfoTitle": { - "message": "Copy info - $ITEMNAME$", + "message": "کپی اطلاعات - $ITEMNAME$", "description": "Title for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -4307,7 +4315,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "کپی یادداشت - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4317,7 +4325,7 @@ } }, "moreOptionsLabel": { - "message": "More options, $ITEMNAME$", + "message": "گزینه‌های بیشتر، $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4327,7 +4335,7 @@ } }, "moreOptionsTitle": { - "message": "More options - $ITEMNAME$", + "message": "گزینه‌های بیشتر - $ITEMNAME$", "description": "Title for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4337,7 +4345,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "مشاهده آیتم - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4347,7 +4355,7 @@ } }, "viewItemTitleWithField": { - "message": "View item - $ITEMNAME$ - $FIELD$", + "message": "مشاهده مورد - $ITEMNAME$ - $FIELD$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4361,7 +4369,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "پر کردن خودکار - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4371,7 +4379,7 @@ } }, "autofillTitleWithField": { - "message": "Autofill - $ITEMNAME$ - $FIELD$", + "message": "پر کردن خودکار - $ITEMNAME$ - $FIELD$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4385,7 +4393,7 @@ } }, "copyFieldValue": { - "message": "Copy $FIELD$, $VALUE$", + "message": "کپی $FIELD$، $VALUE$", "description": "Title for a button that copies a field value to the clipboard.", "placeholders": { "field": { @@ -4399,40 +4407,40 @@ } }, "noValuesToCopy": { - "message": "No values to copy" + "message": "هیچ مقداری برای کپی وجود ندارد" }, "assignToCollections": { - "message": "Assign to collections" + "message": "اختصاص به مجموعه‌ها" }, "copyEmail": { - "message": "Copy email" + "message": "کپی ایمیل" }, "copyPhone": { - "message": "Copy phone" + "message": "کپی تلفن" }, "copyAddress": { - "message": "Copy address" + "message": "کپی آدرس" }, "adminConsole": { - "message": "Admin Console" + "message": "کنسول مدیر" }, "accountSecurity": { - "message": "Account security" + "message": "امنیت حساب کاربری" }, "notifications": { - "message": "Notifications" + "message": "اعلان‌ها" }, "appearance": { - "message": "Appearance" + "message": "ظاهر" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "خطا در اختصاص مجموعه هدف." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "خطا در اختصاص پوشه هدف." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "مشاهده موارد در $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4442,7 +4450,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "بازگشت به $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4452,10 +4460,10 @@ } }, "new": { - "message": "New" + "message": "جدید" }, "removeItem": { - "message": "Remove $NAME$", + "message": "حذف $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -4465,56 +4473,56 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "موارد بدون پوشه" }, "itemDetails": { - "message": "Item details" + "message": "جزئیات مورد" }, "itemName": { - "message": "Item name" + "message": "نام مورد" }, "organizationIsDeactivated": { - "message": "Organization is deactivated" + "message": "سازمان غیرفعال شده است" }, "owner": { - "message": "Owner" + "message": "مالک" }, "selfOwnershipLabel": { - "message": "You", + "message": "شما", "description": "Used as a label to indicate that the user is the owner of an item." }, "contactYourOrgAdmin": { - "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." + "message": "به موردهای موجود در سازمان‌های غیرفعال نمی‌توان دسترسی داشت. برای دریافت کمک با مالک سازمان تماس بگیرید." }, "additionalInformation": { - "message": "Additional information" + "message": "اطلاعات بیشتر" }, "itemHistory": { - "message": "Item history" + "message": "تاریخچه مورد" }, "lastEdited": { - "message": "Last edited" + "message": "آخرین ویرایش" }, "ownerYou": { - "message": "Owner: You" + "message": "مالک: شما" }, "linked": { - "message": "Linked" + "message": "پیوند شده" }, "copySuccessful": { - "message": "Copy Successful" + "message": "کپی موفق بود" }, "upload": { - "message": "Upload" + "message": "بارگذاری" }, "addAttachment": { - "message": "Add attachment" + "message": "افزودن پیوست" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "بیشترین حجم پرونده ۵۰۰ مگابایت است" }, "deleteAttachmentName": { - "message": "Delete attachment $NAME$", + "message": "حذف پیوست $NAME$", "placeholders": { "name": { "content": "$1", @@ -4523,7 +4531,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "بارگیری $NAME$", "placeholders": { "name": { "content": "$1", @@ -4532,52 +4540,52 @@ } }, "downloadBitwarden": { - "message": "Download Bitwarden" + "message": "بارگیری Bitwarden" }, "downloadBitwardenOnAllDevices": { - "message": "Download Bitwarden on all devices" + "message": "Bitwarden را روی همه دستگاه‌ها بارگیری کنید" }, "getTheMobileApp": { - "message": "Get the mobile app" + "message": "دریافت برنامه‌ی تلفن همراه" }, "getTheMobileAppDesc": { - "message": "Access your passwords on the go with the Bitwarden mobile app." + "message": "با برنامه موبایل Bitwarden، به کلمات عبور خود در هر زمان و مکان دسترسی داشته باشید." }, "getTheDesktopApp": { - "message": "Get the desktop app" + "message": "برنامه دسکتاپ را دریافت کنید" }, "getTheDesktopAppDesc": { - "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + "message": "بدون استفاده از مرورگر به گاوصندوق خود دسترسی پیدا کنید و سپس باز کردن قفل با بیومتریک را تنظیم کنید تا باز کردن سریع‌تر قفل در اپ دسکتاپ و افزونه مرورگر انجام شود." }, "downloadFromBitwardenNow": { - "message": "Download from bitwarden.com now" + "message": "هم‌اکنون از bitwarden.com بارگیری کنید" }, "getItOnGooglePlay": { - "message": "Get it on Google Play" + "message": "از Google Play دریافت کنید" }, "downloadOnTheAppStore": { - "message": "Download on the App Store" + "message": "از AppStore بارگیری کنید" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "آیا مطمئن هستید که می‌خواهید این پرونده پیوست را به‌طور دائمی حذف کنید؟" }, "premium": { - "message": "Premium" + "message": "پرمیوم" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "سازمان‌های رایگان نمی‌توانند از پرونده‌های پیوست استفاده کنند" }, "filters": { - "message": "Filters" + "message": "فیلترها" }, "filterVault": { - "message": "Filter vault" + "message": "فیلتر گاوصندوق" }, "filterApplied": { - "message": "One filter applied" + "message": "یک فیلتر اعمال شده است" }, "filterAppliedPlural": { - "message": "$COUNT$ filters applied", + "message": "$COUNT$ فیلتر اعمال شده است", "placeholders": { "count": { "content": "$1", @@ -4586,16 +4594,16 @@ } }, "personalDetails": { - "message": "Personal details" + "message": "جزئیات شخصی" }, "identification": { - "message": "Identification" + "message": "شناسایی" }, "contactInfo": { - "message": "Contact info" + "message": "اطلاعات مخاطب" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "بارگیری - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4604,23 +4612,23 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "شماره کارت پایان می‌یابد با", "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { - "message": "Login credentials" + "message": "اطلاعات ورود" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "کلید احراز هویت" }, "autofillOptions": { - "message": "Autofill options" + "message": "گزینه‌های پر کردن خودکار" }, "websiteUri": { - "message": "Website (URI)" + "message": "وب‌سایت (نشانی اینترنتی)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "وب‌سایت (نشانی اینترنتی) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -4630,16 +4638,16 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "وب‌سایت اضافه شد" }, "addWebsite": { - "message": "Add website" + "message": "افزودن وب‌سایت" }, "deleteWebsite": { - "message": "Delete website" + "message": "حذف وبسایت" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "پیش‌فرض ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -4649,7 +4657,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "نمایش تطبیق سایت $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4658,7 +4666,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "مخفی کردن تطبیق سایت $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4667,19 +4675,19 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "پر کردن خودکار هنگام بارگذاری صفحه؟" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "تاریخ کارت منقضی شده است" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "اگر تمدید کرده‌اید، اطلاعات کارت را به‌روزرسانی کنید" }, "cardDetails": { - "message": "Card details" + "message": "جزئیات کارت" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "$BRAND$ جزئیات", "placeholders": { "brand": { "content": "$1", @@ -4688,43 +4696,43 @@ } }, "enableAnimations": { - "message": "Enable animations" + "message": "فعال‌سازی انیمیشن‌ها" }, "showAnimations": { - "message": "Show animations" + "message": "نمایش انیمیشن‌ها" }, "addAccount": { - "message": "Add account" + "message": "افزودن حساب کاربری" }, "loading": { - "message": "Loading" + "message": "در حال بارگذاری" }, "data": { - "message": "Data" + "message": "داده‌" }, "passkeys": { - "message": "Passkeys", + "message": "کلیدهای عبور", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "کلمات عبور", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { - "message": "Log in with passkey", + "message": "با کلید عبور وارد شوید", "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { - "message": "Assign" + "message": "اختصاص بدهید" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Only organization members with access to these collections will be able to see the item." + "message": "فقط اعضای سازمانی که به این مجموعه‌ها دسترسی دارند قادر به مشاهده این مورد خواهند بود." }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "فقط اعضای سازمانی که به این مجموعه‌ها دسترسی دارند قادر به مشاهده این موارد خواهند بود." }, "bulkCollectionAssignmentWarning": { - "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "message": "شما $TOTAL_COUNT$ مورد را انتخاب کرده‌اید. نمی‌توانید $READONLY_COUNT$ مورد را به‌روزرسانی کنید زیرا دسترسی ویرایش ندارید.", "placeholders": { "total_count": { "content": "$1", @@ -4736,37 +4744,37 @@ } }, "addField": { - "message": "Add field" + "message": "افزودن فیلد" }, "add": { - "message": "Add" + "message": "افزودن" }, "fieldType": { - "message": "Field type" + "message": "نوع فیلد" }, "fieldLabel": { - "message": "Field label" + "message": "برچسب فیلد" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "برای داده‌هایی مانند سوالات امنیتی از فیلدهای متنی استفاده کنید" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "برای داده‌های حساس مانند کلمه عبور از فیلدهای مخفی استفاده کنید" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "اگر می‌خواهید فیلدهای تیک‌دار فرم را به‌صورت خودکار پر کنید، مانند گزینه به یاد سپردن ایمیل، از کادرهای انتخاب استفاده کنید" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "وقتی در پر کردن خودکار برای یک وب‌سایت خاص به مشکل برخوردید، از فیلد مرتبط استفاده کنید." }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "شناسه Html، نام، aria-label یا محل نگهدار فیلد را وارد کنید." }, "editField": { - "message": "Edit field" + "message": "ویرایش فیلد" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "ویرایش $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4775,7 +4783,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "حذف $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4784,7 +4792,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ افزوده شد", "placeholders": { "label": { "content": "$1", @@ -4793,7 +4801,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "مرتب‌سازی مجدد $LABEL$. برای جابجایی مورد به بالا یا پایین از کلیدهای جهت‌نما استفاده کنید.", "placeholders": { "label": { "content": "$1", @@ -4802,10 +4810,10 @@ } }, "reorderWebsiteUriButton": { - "message": "Reorder website URI. Use arrow key to move item up or down." + "message": "مرتب‌سازی مجدد نشانی اینترنتی وب‌سایت. برای جابجایی مورد به بالا یا پایین از کلیدهای جهت‌نما استفاده کنید." }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ به بالا منتقل شد، موقعیت $INDEX$ از $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4822,13 +4830,13 @@ } }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "مجموعه‌ها را برای اختصاص انتخاب کنید" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "۱ مورد به طور دائمی به سازمان انتخاب شده منتقل خواهد شد. شما دیگر مالک این مورد نخواهید بود." }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ مورد به طور دائمی به سازمان انتخاب شده منتقل خواهند شد. شما دیگر مالک این موارد نخواهید بود.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4837,7 +4845,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "۱ مورد به طور دائمی به $ORG$ منتقل خواهد شد. شما دیگر مالک این مورد نخواهید بود.", "placeholders": { "org": { "content": "$1", @@ -4846,7 +4854,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ مورد به طور دائمی به $ORG$ منتقل خواهند شد. شما دیگر مالک این موارد نخواهید بود.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4859,13 +4867,13 @@ } }, "successfullyAssignedCollections": { - "message": "Successfully assigned collections" + "message": "مجموعه‌ها با موفقیت اختصاص داده شدند" }, "nothingSelected": { - "message": "You have not selected anything." + "message": "شما چیزی را انتخاب نکرده اید." }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "موارد به $ORGNAME$ منتقل شدند", "placeholders": { "orgname": { "content": "$1", @@ -4874,7 +4882,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "مورد به $ORGNAME$ منتقل شد", "placeholders": { "orgname": { "content": "$1", @@ -4883,7 +4891,7 @@ } }, "reorderFieldDown": { - "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ به پایین منتقل شد، موقعیت $INDEX$ از $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4900,46 +4908,46 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "مکان مورد" }, "fileSend": { - "message": "File Send" + "message": "پرونده ارسال" }, "fileSends": { - "message": "File Sends" + "message": "پرونده ارسال‌ها" }, "textSend": { - "message": "Text Send" + "message": "ارسال متن" }, "textSends": { - "message": "Text Sends" + "message": "ارسال‌های متن" }, "accountActions": { - "message": "Account actions" + "message": "فعالیت‌های حساب کاربری" }, "showNumberOfAutofillSuggestions": { - "message": "Show number of login autofill suggestions on extension icon" + "message": "نمایش تعداد پیشنهادهای پر کردن خودکار ورود در آیکون افزونه" }, "showQuickCopyActions": { - "message": "Show quick copy actions on Vault" + "message": "نمایش عملیات کپی سریع در گاوصندوق" }, "systemDefault": { - "message": "System default" + "message": "پیش‌فرض سیستم" }, "enterprisePolicyRequirementsApplied": { - "message": "Enterprise policy requirements have been applied to this setting" + "message": "الزامات سیاست سازمانی روی این تنظیم اعمال شده است" }, "sshPrivateKey": { - "message": "Private key" + "message": "کلید خصوصی" }, "sshPublicKey": { - "message": "Public key" + "message": "کلید عمومی" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "اثر انگشت" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "نوع کلید" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -4954,61 +4962,61 @@ "message": "RSA 4096-Bit" }, "retry": { - "message": "Retry" + "message": "تلاش مجدد" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "حداقل مهلت زمانی سفارشی ۱ دقیقه است." }, "additionalContentAvailable": { - "message": "Additional content is available" + "message": "محتوای اضافی در دسترس است" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "پرونده در دستگاه ذخیره شد. از بخش بارگیری‌های دستگاه خود مدیریت کنید." }, "showCharacterCount": { - "message": "Show character count" + "message": "نمایش تعداد کاراکترها" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "مخفی کردن تعداد کاراکترها" }, "itemsInTrash": { - "message": "Items in trash" + "message": "موراد در سطل زباله" }, "noItemsInTrash": { - "message": "No items in trash" + "message": "موردی در سطل زباله نیست" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "مواردی که حذف می‌کنید اینجا نمایش داده می‌شوند و پس از ۳۰ روز به طور دائمی حذف خواهند شد" }, "trashWarning": { - "message": "Items that have been in trash more than 30 days will automatically be deleted" + "message": "مواردی که بیش از ۳۰ روز در سطل زباله بوده‌اند به‌طور خودکار حذف خواهند شد" }, "restore": { - "message": "Restore" + "message": "بازیابی" }, "deleteForever": { - "message": "Delete forever" + "message": "حذف برای همیشه" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "شما مجوز ویرایش این مورد را ندارید" }, "biometricsStatusHelptextUnlockNeeded": { - "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + "message": "بازکردن با بیومتریک در دسترس نیست زیرا ابتدا باید بازکردن قفل با کد پین یا کلمه عبور انجام شود." }, "biometricsStatusHelptextHardwareUnavailable": { - "message": "Biometric unlock is currently unavailable." + "message": "باز کردن قفل با بیومتریک در حال حاضر در دسترس نیست." }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "باز کردن قفل با بیومتریک به دلیل پیکربندی نادرست پرونده‌های سیستم در دسترس نیست." }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "باز کردن قفل با بیومتریک به دلیل پیکربندی نادرست پرونده‌های سیستم در دسترس نیست." }, "biometricsStatusHelptextDesktopDisconnected": { - "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + "message": "باز کردن قفل با بیومتریک در دسترس نیست زیرا برنامه دسکتاپ Bitwarden بسته است." }, "biometricsStatusHelptextNotEnabledInDesktop": { - "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "message": "باز کردن قفل با بیومتریک در دسترس نیست زیرا برای $EMAIL$ در برنامه دسکتاپ Bitwarden فعال نشده است.", "placeholders": { "email": { "content": "$1", @@ -5017,208 +5025,217 @@ } }, "biometricsStatusHelptextUnavailableReasonUnknown": { - "message": "Biometric unlock is currently unavailable for an unknown reason." + "message": "باز کردن قفل با بیومتریک در حال حاضر به دلیل نامعلومی در دسترس نیست." + }, + "unlockVault": { + "message": "قفل گاوصندوق خود را در چند ثانیه باز کنید" + }, + "unlockVaultDesc": { + "message": "می‌توانید تنظیمات باز کردن قفل و زمان‌بندی خودکار بسته شدن گاوصندوق را سفارشی کنید تا سریع‌تر به گاوصندوق خود دسترسی پیدا کنید." + }, + "unlockPinSet": { + "message": "بازکردن قفل کد پین تنظیم شد" }, "authenticating": { - "message": "Authenticating" + "message": "در حال احراز هویت" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "پر کردن کلمه عبور تولید شده", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "کلمه عبور تولید شد", "description": "Notification message for when a password has been regenerated" }, "saveToBitwarden": { - "message": "Save to Bitwarden", + "message": "ذخیره در Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "فاصله", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "تیلد", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "بک‌تیک", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "علامت تعجب", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "علامت @", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "علامت #", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "علامت دلار", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "علامت ٪", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "علامت ^", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "علامت &", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "علامت *", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "پرانتز چپ", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "پرانتز راست", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "خط زیر", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "خط تیره", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "به علاوه", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "مساوی", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "آکولادِ چپ", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "آکولادِ راست", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "کروشه‌ی چپ", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "کروشه‌ی راست", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "علامت |", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "بک اسلش", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "دو نقطه", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "نقطه ویرگول", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "دابل کوت", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "سینگل کوت", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "کمتر از", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "بیش‌تر از", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "کاما", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "دوره", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "علامت سوال", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "ممیز", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "حروف کوچک" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "حروف بزرگ" }, "generatedPassword": { - "message": "Generated password" + "message": "کلمه عبور تولید شد" }, "compactMode": { - "message": "Compact mode" + "message": "حالت فشرده" }, "beta": { - "message": "Beta" + "message": "آزمایشی" }, "extensionWidth": { - "message": "Extension width" + "message": "عرض افزونه" }, "wide": { - "message": "Wide" + "message": "عریض" }, "extraWide": { - "message": "Extra wide" + "message": "خیلی عریض" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "کلمه عبور وارد شده اشتباه است." }, "importSshKey": { - "message": "Import" + "message": "درون ریزی" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "تأیید کلمه عبور" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "کلمه عبور کلید SSH را وارد کنید." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "کلمه عبور را وارد کنید" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "کلید SSH نامعتبر است" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "نوع کلید SSH پشتیبانی نمی‌شود" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "وارد کردن کلید از حافظه موقت" }, "sshKeyImported": { - "message": "SSH key imported successfully" + "message": "کلید SSH با موفقیت وارد شد" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "نمی‌توانید مجموعه‌هایی را که فقط دسترسی مشاهده دارند حذف کنید: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -5227,121 +5244,138 @@ } }, "updateDesktopAppOrDisableFingerprintDialogTitle": { - "message": "Please update your desktop application" + "message": "لطفاً برنامه دسکتاپ خود را به‌روزرسانی کنید" }, "updateDesktopAppOrDisableFingerprintDialogMessage": { - "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + "message": "برای استفاده از باز کردن قفل با بیومتریک، لطفاً برنامه دسکتاپ خود را به‌روزرسانی کنید یا باز کردن قفل با اثر انگشت را در تنظیمات دسکتاپ غیرفعال کنید." }, "changeAtRiskPassword": { - "message": "Change at-risk password" + "message": "تغییر کلمه عبور در معرض خطر" }, "settingsVaultOptions": { - "message": "Vault options" + "message": "گزینه‌های گاوصندوق" }, "emptyVaultDescription": { - "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + "message": "گاوصندوق تنها کلمات عبور را محافظت نمی‌کند. ورودهای امن، شناسه‌ها، کارت‌ها و یادداشت‌ها را نیز به صورت امن در اینجا ذخیره کنید." }, "introCarouselLabel": { - "message": "Welcome to Bitwarden" + "message": "به Bitwarden خوش آمدید" }, "securityPrioritized": { - "message": "Security, prioritized" + "message": "امنیت، در اولویت" }, "securityPrioritizedBody": { - "message": "Save logins, cards, and identities to your secure vault. Bitwarden uses zero-knowledge, end-to-end encryption to protect what’s important to you." + "message": "ورودها، کارت‌ها و هویت‌ها را در گاوصندوق امن خود ذخیره کنید. Bitwarden از رمزگذاری انتها به انتهای دانش صفر برای محافظت از آنچه برای شما مهم است استفاده می‌کند." }, "quickLogin": { - "message": "Quick and easy login" + "message": "ورود سریع و آسان" }, "quickLoginBody": { - "message": "Set up biometric unlock and autofill to log into your accounts without typing a single letter." + "message": "قفل بیومتریک و پر کردن خودکار را تنظیم کنید تا بدون وارد کردن حتی یک حرف وارد حساب‌های خود شوید." }, "secureUser": { - "message": "Level up your logins" + "message": "ورودهای خود را ارتقا دهید" }, "secureUserBody": { - "message": "Use the generator to create and save strong, unique passwords for all your accounts." + "message": "از تولید کننده برای ایجاد و ذخیره کلمه‌های عبور قوی و منحصر به فرد برای تمام حساب‌های کاربری خود استفاده کنید." }, "secureDevices": { - "message": "Your data, when and where you need it" + "message": "داده‌های شما، زمانی که نیاز دارید و در جایی که نیاز دارید" }, "secureDevicesBody": { - "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + "message": "کلمه‌های عبور نامحدود را در دستگاه‌های نامحدود با اپلیکیشن‌های موبایل، مرورگر و دسکتاپ Bitwarden ذخیره کنید." }, "nudgeBadgeAria": { - "message": "1 notification" + "message": "۱ اعلان" }, "emptyVaultNudgeTitle": { - "message": "Import existing passwords" + "message": "درون ریزی کلمات عبور موجود" }, "emptyVaultNudgeBody": { - "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + "message": "از ابزار درون ریزی استفاده کنید تا ورودها را سریعاً به Bitwarden منتقل کنید بدون نیاز به افزودن دستی آن‌ها." }, "emptyVaultNudgeButton": { - "message": "Import now" + "message": "الان درودن ریزی کن" }, "hasItemsVaultNudgeTitle": { - "message": "Welcome to your vault!" + "message": "به گاوصندوق خود خوش آمدید!" }, "hasItemsVaultNudgeBodyOne": { - "message": "Autofill items for the current page" + "message": "پر کردن خودکار موارد برای صفحه فعلی" }, "hasItemsVaultNudgeBodyTwo": { - "message": "Favorite items for easy access" + "message": "موارد مورد علاقه برای دسترسی آسان" }, "hasItemsVaultNudgeBodyThree": { - "message": "Search your vault for something else" + "message": "در گاوصندوق خود به دنبال چیز دیگری بگردید" }, "newLoginNudgeTitle": { - "message": "Save time with autofill" + "message": "با پر کردن خودکار در وقت خود صرفه جویی کنید" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "شامل یک", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "وب‌سایت", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "تا این ورود به عنوان پیشنهاد پر کردن خودکار ظاهر شود.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newCardNudgeTitle": { - "message": "Seamless online checkout" + "message": "پرداخت آنلاین بدون وقفه" }, "newCardNudgeBody": { - "message": "With cards, easily autofill payment forms securely and accurately." + "message": "با کارت‌ها، فرم‌های پرداخت را به‌راحتی و با امنیت و دقت پر کنید." }, "newIdentityNudgeTitle": { - "message": "Simplify creating accounts" + "message": "ساخت حساب‌های کاربری را ساده کنید" }, "newIdentityNudgeBody": { - "message": "With identities, quickly autofill long registration or contact forms." + "message": "با هویت‌ها، به سرعت فرم‌های طولانی ثبت‌نام یا تماس را پر کنید." }, "newNoteNudgeTitle": { - "message": "Keep your sensitive data safe" + "message": "اطلاعات حساس خود را ایمن نگه دارید" }, "newNoteNudgeBody": { - "message": "With notes, securely store sensitive data like banking or insurance details." + "message": "با یادداشت‌ها، اطلاعات حساسی مانند جزئیات بانکی یا بیمه را به‌صورت ایمن ذخیره کنید." }, "newSshNudgeTitle": { - "message": "Developer-friendly SSH access" + "message": "دسترسی SSH مناسب برای توسعه‌دهندگان" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "کلیدهای خود را ذخیره کنید و با عامل SSH برای احراز هویت سریع و رمزگذاری‌شده متصل شوید.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Learn more about SSH agent", + "message": "اطلاعات بیشتر درباره عامل SSH را بیاموزید", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "ساخت سریع کلمات عبور" + }, + "generatorNudgeBodyOne": { + "message": "به‌راحتی کلمات عبور قوی و منحصر به فرد ایجاد کنید با کلیک روی", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "برای کمک به حفظ امنیت ورودهای شما.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "با کلیک روی دکمه تولید رمز عبور، به‌راحتی کلمات عبور قوی و منحصر به‌ فرد ایجاد کنید تا ورودهای شما ایمن باقی بمانند.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { - "message": "You do not have permissions to view this page. Try logging in with a different account." + "message": "شما اجازه دسترسی به این صفحه را ندارید. لطفاً با حساب کاربری دیگری وارد شوید." } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index ead59384ff8..8e5eede202a 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -1072,7 +1072,7 @@ "description": "Aria label for the view button in notification bar confirmation message" }, "notificationNewItemAria": { - "message": "New Item, opens in new window", + "message": "Uusi kohde, avautuu uudessa ikkunassa", "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" }, "notificationEditTooltip": { @@ -1606,7 +1606,7 @@ "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." }, "turnOffBrowserAutofill": { - "message": "Turn off $BROWSER$ autofill", + "message": "Poista automaattitäyttö käytöstä selaimessa $BROWSER$", "placeholders": { "browser": { "content": "$1", @@ -1615,7 +1615,7 @@ } }, "turnOffAutofill": { - "message": "Turn off autofill" + "message": "Poista automaattitäyttö käytöstä" }, "showInlineMenuLabel": { "message": "Näytä automaattitäytön ehdotukset lomakekentissä" @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Yksilöllistä tunnistetta ei löytynyt." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ käyttää kertakirjautumista (SSO) itse ylläpidetyllä avainpalvelimella. Organisaation jäsenet eivät enää tarvitse pääsalasanaa kirjautumiseen.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Poistu organisaatiosta" @@ -3584,16 +3584,16 @@ "message": "Laitteeseen luotettu" }, "trustOrganization": { - "message": "Trust organization" + "message": "Luota organisaatioon" }, "trust": { - "message": "Trust" + "message": "Luota" }, "doNotTrust": { - "message": "Do not trust" + "message": "Älä luota" }, "organizationNotTrusted": { - "message": "Organization is not trusted" + "message": "Organisaatio ei ole luotettu" }, "emergencyAccessTrustWarning": { "message": "For the security of your account, only confirm if you have granted emergency access to this user and their fingerprint matches what is displayed in their account" @@ -3605,7 +3605,7 @@ "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." }, "trustUser": { - "message": "Trust user" + "message": "Luota käyttäjään" }, "sendsNoItemsTitle": { "message": "Aktiivisia Sendejä ei ole", @@ -3615,6 +3615,14 @@ "message": "Sendillä voit jakaa salattuja tietoja turvallisesti kenelle tahansa.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Syöte vaaditaan." }, @@ -4532,19 +4540,19 @@ } }, "downloadBitwarden": { - "message": "Download Bitwarden" + "message": "Lataa Bitwarden" }, "downloadBitwardenOnAllDevices": { - "message": "Download Bitwarden on all devices" + "message": "Lataa Bitwarden kaikille laitteille" }, "getTheMobileApp": { - "message": "Get the mobile app" + "message": "Hanki mobiilisovellus" }, "getTheMobileAppDesc": { "message": "Access your passwords on the go with the Bitwarden mobile app." }, "getTheDesktopApp": { - "message": "Get the desktop app" + "message": "Hanki työpöytäsovellus" }, "getTheDesktopAppDesc": { "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." @@ -4553,10 +4561,10 @@ "message": "Download from bitwarden.com now" }, "getItOnGooglePlay": { - "message": "Get it on Google Play" + "message": "Hanki se Google Playstä" }, "downloadOnTheAppStore": { - "message": "Download on the App Store" + "message": "Lataa App Storesta" }, "permanentlyDeleteAttachmentConfirmation": { "message": "Haluatko varmasti poistaa liitteen pysyvästi?" @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrinen avaus ei ole tällä hetkellä käytettävissä tuntemattomasta syystä." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Todennetaan" }, @@ -5236,7 +5253,7 @@ "message": "Vaihda vaarantunut salasana" }, "settingsVaultOptions": { - "message": "Vault options" + "message": "Holvin asetukset" }, "emptyVaultDescription": { "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." @@ -5269,19 +5286,19 @@ "message": "Tallenna rajattomasti salasanoja, rajattomalla määrällä laitteita, Bitwardenin mobiili-, selain- ja työpöytäsovelluksilla." }, "nudgeBadgeAria": { - "message": "1 notification" + "message": "1 ilmoitus" }, "emptyVaultNudgeTitle": { - "message": "Import existing passwords" + "message": "Tuo olemassa olevat salasanat" }, "emptyVaultNudgeBody": { "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." }, "emptyVaultNudgeButton": { - "message": "Import now" + "message": "Tuo nyt" }, "hasItemsVaultNudgeTitle": { - "message": "Welcome to your vault!" + "message": "Tervetuloa holviisi!" }, "hasItemsVaultNudgeBodyOne": { "message": "Autofill items for the current page" @@ -5293,7 +5310,7 @@ "message": "Search your vault for something else" }, "newLoginNudgeTitle": { - "message": "Save time with autofill" + "message": "Säästä aikaa automaattitäytöllä" }, "newLoginNudgeBodyOne": { "message": "Include a", @@ -5301,7 +5318,7 @@ "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "Verkkosivusto", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index a7734899843..e8e45249773 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Walang natagpuang natatanging nag-identipikar." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ ay gumagamit ng SSO na may sariling-hosted na key server. Walang kinakailangang master password para mag-log in para sa mga miyembro ng organisasyon na ito.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Umalis sa organisasyon" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 8a825082b5f..736fd21349e 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Aucun identifiant unique trouvé." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ utilise le SSO avec un serveur de clés auto-hébergé. Un mot de passe principal n'est plus nécessaire aux membres de cette organisation pour se connecter.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Quitter l'organisation" @@ -3615,6 +3615,14 @@ "message": "Utilisez Send pour partager en toute sécurité des informations chiffrées avec tout le monde.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Saisie requise." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Le déverrouillage par biométrie n'est pas disponible actuellement pour une raison inconnue." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authentification" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 91a5121db16..e10287f3e7b 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Non se atopou ningún identificador único." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ emprega SSO con un servidor de claves propio. Os membros da organización xa non precisan dun contrasinal mestre para iniciar sesión.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Deixar a organización" @@ -3615,6 +3615,14 @@ "message": "Usar send para compartir información cifrada con quen queiras.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Requírese algunha entrada." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "O desbloqueo biométrico non está dispoñible por algunha razón non prevista." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autenticando" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 2e42a819b65..1b264d9a70a 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "לא נמצא מזהה ייחודי." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ משתמש/ת ב־SSO עם שרת מפתחות באירוח עצמי. סיסמה ראשית כבר לא נדרשת כדי להיכנס עבור חברים של ארגון זה.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "עזוב ארגון" @@ -3615,6 +3615,14 @@ "message": "השתמש בסֵנְד כדי לשתף באופן מאובטח מידע מוצפן עם כל אחד.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "נדרש קלט." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "ביטול נעילה ביומטרי אינו זמין כעת מסיבה לא ידועה." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "מאמת" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 8af1aa70cc3..61c0fbe9963 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 557e465ea19..ad32937c740 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nije nađen jedinstveni identifikator." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ koristi jedinstvenu prijavu SSO s vlastitim poslužiteljem. Članovima organizacije glavna lozinka više nije potrebna.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Napusti organizaciju" @@ -3615,6 +3615,14 @@ "message": "Koristi Send za sigurno slanje šifriranih podataka bilo kome.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Potreban je unos." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrijsko otključavanje trenutno nije dostupno iz nepoznatog razloga." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autentifikacija" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index b47093bc2ee..50607787cd7 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nincs egyedi azonosító." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ jelenleg saját tárolású aláíráskulcsú SSO szervert használ. A mesterjelszó a továbbiakban nem szükséges a szervezeti tagsági bejelentkezéshez.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A következő szervezet tagjai számára már nincs szükség mesterjelszóra. Erősítsük meg az alábbi tartományt a szervezet adminisztrátorával." + }, + "organizationName": { + "message": "Szervezet neve" + }, + "keyConnectorDomain": { + "message": "Key Connector tartomány" }, "leaveOrganization": { "message": "Szervezet elhagyása" @@ -3615,6 +3615,14 @@ "message": "A Send használatával biztonságosan megoszthatjuk a titkosított információkat bárkivel.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Érzékeny információt küldése biztonságosan", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Fájlok vagy adatok megosztása biztonságosan bárkivel, bármilyen platformon. Az információk titkosítva maradnak a végpontokon, korlátozva a kitettséget.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Az adatbevitel kötelező." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "A biometrikus feloldás jelenleg ismeretlen okból nem érhető el." }, + "unlockVault": { + "message": "A széf feloldása másodpercek alatt" + }, + "unlockVaultDesc": { + "message": "Testreszabhatjuk a feloldási és időtúllépési beállításokat a széf gyorsabb elérése érdekében." + }, + "unlockPinSet": { + "message": "PIN beállítás feloldása" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Jelszavak gyors létrehozása" + }, + "generatorNudgeBodyOne": { + "message": "Könnyen létrehozhatunk erős és egyedi jelszavakat a gombra kattintva.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "a bejelentkezések biztonságának megőrzéséhez.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Könnyedén hozhatunk létre erős és egyedi jelszavakat a Jelszó generálása gombra kattintva, amely segít megőrizni a bejelentkezések biztonságát.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Nincs jogosultság az oldal megtekintéséhez. Próbáljunk meg másik fiókkal bejelentkezni." } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 03e47fca575..a12137d696a 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Tidak ada pengidentifikasi unik yang ditemukan." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ menggunakan SSO dengan server kunci yang dihosting sendiri. Kata sandi utama tidak lagi diperlukan untuk masuk untuk anggota organisasi ini.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Tinggalkan Organisasi" @@ -3615,6 +3615,14 @@ "message": "Gunakan Send untuk membagikan informasi terenkripsi secara aman dengan siapapun.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Masukan ini harus diisi." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index d3d0dc9ec43..9a5d2c65bb6 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nessun identificatore univoco trovato." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ sta usando SSO con un server self-hosted. Una password principale non è più necessaria per accedere per i membri di questa organizzazione.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lascia organizzazione" @@ -3615,6 +3615,14 @@ "message": "Utilizza un Send per condividere in modo sicuro le informazioni con qualsiasi utente.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input obbligatorio." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Lo sblocco biometrico non è attualmente disponibile per un motivo sconosciuto." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autenticazione" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index e9c22c1286f..5c235bdea43 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "一意の識別子が見つかりませんでした。" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ は自己ホストの鍵サーバで SSO を使用しています。この組織のメンバーのログインにマスターパスワードは必要ありません。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "組織から脱退する" @@ -3615,6 +3615,14 @@ "message": "Send を使用すると暗号化された情報を誰とでも安全に共有できます。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "入力が必要です。" }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "生体認証によるロック解除は、不明な理由により現在利用できません。" }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "認証中" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index d8397db5da8..7f36bb3568f 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "ავთენტიკაცია" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index f436c45ab75..4775d1f7af0 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 0459e007126..ac36d911e89 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 8add57d61ec..5a496cde98a 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "고유 식별자를 찾을 수 없습니다." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 조직은 자체 호스팅 키 서버로 SSO를 사용하고 있습니다 이 조직의 멤버들은 로그인할 때에 마스터 비밀번호를 필요로 하지 않습니다.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "조직 나가기" @@ -3615,6 +3615,14 @@ "message": "Send를 사용하여 암호화된 정보를 어느 사람과도 안전하게 공유합니다.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "입력이 필요합니다." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "인증 중" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 06ff7cda8fa..f790c226437 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -3,7 +3,7 @@ "message": "Bitwarden" }, "appLogoLabel": { - "message": "Bitwarden logo" + "message": "Bitwarden logotipas" }, "extName": { "message": "„Bitwarden“ slaptažodžių tvarkyklė", @@ -23,16 +23,16 @@ "message": "Sukurti paskyrą" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Pirmą kartą Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Prisijungti naudojant prieigos raktą" }, "useSingleSignOn": { "message": "Use single sign-on" }, "welcomeBack": { - "message": "Welcome back" + "message": "Sveiki sugrįžę" }, "setAStrongPassword": { "message": "Nustatyti stiprų slaptažodį" @@ -84,7 +84,7 @@ "message": "Pagrindinio slaptažodžio užuomina (neprivaloma)" }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "Slaptažodžio stiprumas $SCORE$", "placeholders": { "score": { "content": "$1", @@ -93,10 +93,10 @@ } }, "joinOrganization": { - "message": "Join organization" + "message": "Prisijungti prie organizacijos" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Prisijungti prie $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -105,7 +105,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "Baigėte prisijungimą prie organizacijos nustatant pagrindinį slaptažodį." }, "tab": { "message": "Skirtukas" @@ -132,7 +132,7 @@ "message": "Kopijuoti slaptažodį" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "Kopijuoti slaptažodžio frazę" }, "copyNote": { "message": "Kopijuoti pastabą" @@ -150,31 +150,31 @@ "message": "Kopijuoti saugos kodą" }, "copyName": { - "message": "Copy name" + "message": "Kopijuoti vardą" }, "copyCompany": { - "message": "Copy company" + "message": "Kopijuoti įmonę" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Kopijuoti socialinės apsaugos numerį" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Kopijuoti paso numerį" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Kopijuoti licenzijos numerį" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "Kopijuoti privatų raktą" }, "copyPublicKey": { - "message": "Copy public key" + "message": "Kopijuoti viešą raktą" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "Kopijuoti piršto antspaudą" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "Kopijuoti $FIELD$", "placeholders": { "field": { "content": "$1", @@ -183,13 +183,13 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Kopijuoti svetainę" }, "copyNotes": { - "message": "Copy notes" + "message": "Kopijuoti pastabas" }, "copy": { - "message": "Copy", + "message": "Kopijuoti", "description": "Copy to clipboard" }, "fill": { @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Unikalus identifikatorius nerastas." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ naudoja SSO su savarankiškai sukurtu raktų serveriu. Pagrindinis slaptažodis nebėra reikalingas norint šios organizacijos nariams prisijungti.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Palikti organizaciją" @@ -3615,6 +3615,14 @@ "message": "Naudokite „Send“, kad saugiai bendrintumėte užšifruotą informaciją su bet kuriuo asmeniu.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index e9044f4c041..f47b5f7645e 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nav atrasts neviens neatkārtojams identifikators" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ izmanto vienoto pieteikšanos ar pašmitinātu atslēgu serveri. Tās dalībniekiem vairs nav nepieciešama galvenā parole, lai pieteiktos.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Galvenā parole vairs nav nepieciešama turpmāk minētās apvienības dalībniekiem. Lūgums saskaņot zemāk esošo domēnu ar savas apvienības pārvaldītāju." + }, + "organizationName": { + "message": "Apvienības nosaukums" + }, + "keyConnectorDomain": { + "message": "Key Connector domēns" }, "leaveOrganization": { "message": "Pamest apvienību" @@ -3615,6 +3615,14 @@ "message": "Send ir izmantojams, lai ar ikvienu droši kopīgotu šifrētu informāciju.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Drošā veidā nosūti jūtīgu informāciju", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Drošā veidā kopīgo datnes un datus ar ikvienu jebkurā platformā! Tava informācija paliks pilnībā šifrēta, vienlaikus ierobežojot riskantumu.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Jāievada vērtība." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Atslēgšana ar biometriju pašlaik nav pieejama nezināma iemesla dēļ." }, + "unlockVault": { + "message": "Atslēdz savu glabātavu dažās sekundēs" + }, + "unlockVaultDesc": { + "message": "Tu vari pielāgot savus atslēgšanas un noildzes iestatījumus, lai ātrāk piekļūtu savai glabātavai." + }, + "unlockPinSet": { + "message": "Atslēgšanas PIN iestatīts" + }, "authenticating": { "message": "Autentificē" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Ātra paroļu izveidošana" + }, + "generatorNudgeBodyOne": { + "message": "Vienkārša spēcīgu un neatkārtojamu paroļu izveidošana ar pogu", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": ", lai palīdzētu uzturērt pieteikšanās vienumus drošus.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Vienkārša spēcīgu un neatkārtojamu paroļu izveidošana ar pogu \"Izveidot paroli\", lai palīdzētu uzturēt pieteikšanās vienumus drošus.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Nav atļaujas apskatīt šo lapu. Jāmēģina pieteikties ar citu kontu." } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index e6de809dfbd..ab0120a40e2 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index dd347d822c7..625bf093ba9 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index f436c45ab75..4775d1f7af0 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 62b55eb6501..a11d5de6e2a 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Ingen unik identifikator ble funnet." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ bruker SSO med en selvdrevet nøkkelserver. Et hovedpassord er ikke lenger nødvendig for å logge inn for medlemmer av denne organisasjonen.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Forlat organisasjonen" @@ -3615,6 +3615,14 @@ "message": "Bruk Send til å dele kryptert informasjon med noen.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Inndata er påkrevd." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autentiserer" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index f436c45ab75..4775d1f7af0 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index af637b2f4cd..67a784459b2 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Geen unieke id gevonden." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ gebruikt SSO met een zelf gehoste sleutelserver. Leden van deze organisatie kunnen inloggen zonder hoofdwachtwoord.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Organisatie verlaten" @@ -3615,6 +3615,14 @@ "message": "Gebruik Send voor het veilig delen van versleutelde informatie met wie dan ook.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Gevoelige informatie veilig versturen", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Deel bestanden en gegevens veilig met iedereen, op elk platform. Je informatie blijft end-to-end versleuteld terwijl en blootstelling beperkt.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Invoer vereist." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar om een onbekende reden." }, + "unlockVault": { + "message": "Ontgrendel je kluis in seconden" + }, + "unlockVaultDesc": { + "message": "Je kunt de instellingen voor ontgrendelen en time-out aanpassen om sneller toegang te krijgen tot je kluis." + }, + "unlockPinSet": { + "message": "PIN-code ontgrendelen instellen" + }, "authenticating": { "message": "Aan het inloggen" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Snel wachtwoorden maken" + }, + "generatorNudgeBodyOne": { + "message": "Maak eenvoudig sterke en unieke wachtwoorden door te klikken op", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "om je te helpen je inloggegevens veilig te houden.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Maak eenvoudig sterke en unieke wachtwoorden door op de knop Wachtwoord genereren te klikken om je logins veilig te houden.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Je hebt geen rechten om deze pagina te bekijken. Probeer in te loggen met een ander account." } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index f436c45ab75..4775d1f7af0 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index f436c45ab75..4775d1f7af0 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 16696e5f828..eb391f5e34b 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nie znaleziono unikatowego identyfikatora." }, - "convertOrganizationEncryptionDesc": { - "message": "Organizacja $ORGANIZATION$ używa jednokrotnego logowania SSO z własnym serwerem kluczy. Użytkownicy nie muszą logować się za pomocą hasła głównego.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hasło główne nie jest już wymagane dla członków następującej organizacji. Proszę potwierdzić poniższą domenę u administratora organizacji." + }, + "organizationName": { + "message": "Nazwa organizacji" + }, + "keyConnectorDomain": { + "message": "Domena Key Connector'a" }, "leaveOrganization": { "message": "Opuść organizację" @@ -3615,6 +3615,14 @@ "message": "Użyj wysyłki, aby bezpiecznie dzielić się zaszyfrowanymi informacjami ze wszystkimi.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Wysyłaj bezpiecznie poufne informacje", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Udostępniaj pliki i dane bezpiecznie każdemu, na każdej platformie. Twoje dane pozostaną zaszyfrowane end-to-end przy jednoczesnym ograniczeniu narażenia.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Dane wejściowe są wymagane." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Odblokowanie biometryczne jest obecnie niedostępne z nieznanego powodu." }, + "unlockVault": { + "message": "Odblokuj swój sejf w kilka sekund" + }, + "unlockVaultDesc": { + "message": "Możesz dostosować ustawienia odblokowania i limitu czasu, aby szybciej uzyskać dostęp do sejfu." + }, + "unlockPinSet": { + "message": "Ustaw kod PIN odblokowujący" + }, "authenticating": { "message": "Uwierzytelnianie" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Szybko twórz hasła" + }, + "generatorNudgeBodyOne": { + "message": "Łatwo twórz silne i unikalne hasła, klikając na", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": ", aby pomóc Ci zachować bezpieczeństwo Twoich danych logowania.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Łatwo twórz silne i unikalne hasła, klikając na Wygeneruj Hasło, aby pomóc Ci zachować bezpieczeństwo Twoich danych logowania.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Nie masz uprawnień do przeglądania tej strony. Spróbuj zalogować się na inne konto." } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index e5ca45500a5..c0b83248edf 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nenhum identificador exclusivo encontrado." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ está usando SSO com um servidor de chaves auto-hospedado. Não é mais necessária uma senha mestra para os membros desta organização entrarem.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Sair da Organização" @@ -3615,6 +3615,14 @@ "message": "Use o Send para compartilhar informação criptografa com qualquer um.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Entrada necessária." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "O desbloqueio por biometria está indisponível por razões desconhecidas." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autenticando" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index a4ed095c4db..a144f5767d4 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Não foi encontrado um identificador único." }, - "convertOrganizationEncryptionDesc": { - "message": "A $ORGANIZATION$ está a utilizar o SSO com um servidor de chaves auto-hospedado. Já não é necessária uma palavra-passe mestra para iniciar sessão para os membros desta organização.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Já não é necessária uma palavra-passe mestra para os membros da seguinte organização. Por favor, confirme o domínio abaixo com o administrador da sua organização." + }, + "organizationName": { + "message": "Nome da organização" + }, + "keyConnectorDomain": { + "message": "Domínio do Key Connector" }, "leaveOrganization": { "message": "Sair da organização" @@ -3615,6 +3615,14 @@ "message": "Utilize o Send para partilhar de forma segura informações encriptadas com qualquer pessoa.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Envie informações sensíveis com segurança", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Partilhe ficheiros e dados de forma segura com qualquer pessoa, em qualquer plataforma. As suas informações permanecerão encriptadas ponto a ponto, limitando a exposição.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Campo obrigatório." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "O desbloqueio biométrico está atualmente indisponível por um motivo desconhecido." }, + "unlockVault": { + "message": "Desbloqueie o seu cofre em segundos" + }, + "unlockVaultDesc": { + "message": "Pode personalizar as suas definições de desbloqueio e de tempo limite para aceder mais rapidamente ao seu cofre." + }, + "unlockPinSet": { + "message": "Definição do PIN de desbloqueio" + }, "authenticating": { "message": "A autenticar" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Criar rapidamente palavras-passe" + }, + "generatorNudgeBodyOne": { + "message": "Crie facilmente palavras-passe fortes e únicas clicando em", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "para o ajudar a manter as suas credenciais seguras.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Crie facilmente palavras-passe fortes e únicas clicando no botão Gerar palavra-passe para o ajudar a manter as suas credenciais seguras.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Não tem permissões para ver esta página. Tente iniciar sessão com uma conta diferente." } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 71990435ad2..2aabf825399 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nu a fost găsit niciun identificator unic." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ folosește SSO cu un server de chei autogăzduit. Membrii acestei organizații nu mai au nevoie de o parolă principală pentru autentificare.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Părăsire organizație" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Este necesară o intrare." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 2d23c5352e5..a23ab4172f8 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -206,7 +206,7 @@ "message": "Автозаполнение карты" }, "autoFillIdentity": { - "message": "Автозаполнение личности" + "message": "Автозаполнение личной информации" }, "fillVerificationCode": { "message": "Заполнить код подтверждения" @@ -228,7 +228,7 @@ "message": "Нет карт" }, "noIdentities": { - "message": "Нет личностей" + "message": "Нет личной информации" }, "addLoginMenu": { "message": "Добавить логин" @@ -237,7 +237,7 @@ "message": "Добавить карту" }, "addIdentityMenu": { - "message": "Добавить личность" + "message": "Добавить личную информацию" }, "unlockVaultMenu": { "message": "Разблокировать хранилище" @@ -1037,10 +1037,10 @@ "message": "Всегда показывать личности как предложения автозаполнения при просмотре хранилища" }, "showIdentitiesCurrentTab": { - "message": "Показывать Личности на вкладке" + "message": "Показывать Личную информацию на вкладке" }, "showIdentitiesCurrentTabDesc": { - "message": "Личности будут отображены на вкладке для удобного автозаполнения." + "message": "Личная информация будет отображена на вкладке для удобного автозаполнения." }, "clickToAutofillOnVault": { "message": "Кликните элементы для автозаполнения в режиме просмотра хранилища" @@ -1621,7 +1621,7 @@ "message": "Показывать предположения автозаполнения в полях формы" }, "showInlineMenuIdentitiesLabel": { - "message": "Показывать Личности как предложения" + "message": "Показывать Личную информацию как предложения" }, "showInlineMenuCardsLabel": { "message": "Показывать Карты как предложения" @@ -1699,7 +1699,7 @@ "message": "Автозаполнение последней использованной карты для текущего сайта" }, "commandAutofillIdentityDesc": { - "message": "Автозаполнение последней использованной личности для текущего сайта" + "message": "Автозаполнение последних использованных личных данных для текущего сайта" }, "commandGeneratePasswordDesc": { "message": "Сгенерировать и скопировать новый случайный пароль в буфер обмена." @@ -1857,7 +1857,7 @@ "message": "Полное имя" }, "identityName": { - "message": "Название личности" + "message": "Название личной информации" }, "company": { "message": "Компания" @@ -1989,7 +1989,7 @@ "message": "Карты" }, "identities": { - "message": "Личные данные" + "message": "Личная информация" }, "logins": { "message": "Логины" @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Уникальный идентификатор не найден." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ использует SSO с собственным сервером ключей. Для авторизации пользователям этой организации больше не требуется мастер-пароль.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Мастер-пароль больше не требуется для членов следующей организации. Пожалуйста, подтвердите указанный ниже домен у администратора вашей организации." + }, + "organizationName": { + "message": "Название организации" + }, + "keyConnectorDomain": { + "message": "Домен соединителя ключей" }, "leaveOrganization": { "message": "Покинуть организацию" @@ -3615,6 +3615,14 @@ "message": "Используйте Send для безопасного обмена зашифрованной информацией с кем угодно.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Безопасная отправка конфиденциальной информации", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Безопасно обменивайтесь файлами и данными с кем угодно на любой платформе. Ваша информация надежно шифруется и доступ к ней ограничен.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Необходимо ввести данные." }, @@ -3825,11 +3833,11 @@ "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "Новая личность", + "message": "Новая личная информация", "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { - "message": "Добавление новой личности в хранилище, откроется в новом окне", + "message": "Добавление новой личной информации в хранилище, откроется в новом окне", "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { @@ -4068,7 +4076,7 @@ "message": "Функция пока не поддерживается" }, "yourPasskeyIsLocked": { - "message": "Для использования passkey необходима аутентификация. Для продолжения работы подтвердите свою личность." + "message": "Для использования passkey необходима аутентификация. Для продолжения работы подтвердите вашу личность." }, "multifactorAuthenticationCancelled": { "message": "Многофакторная аутентификация отменена" @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Биометрическая разблокировка в настоящее время недоступна по неизвестной причине." }, + "unlockVault": { + "message": "Разблокируйте свое хранилище за считанные секунды" + }, + "unlockVaultDesc": { + "message": "Вы можете настроить параметры разблокировки и тайм-аута для более быстрого доступа к хранилищу." + }, + "unlockPinSet": { + "message": "Установить PIN--код разблокировки" + }, "authenticating": { "message": "Аутентификация" }, @@ -5248,7 +5265,7 @@ "message": "Безопасность, приоритет" }, "securityPrioritizedBody": { - "message": "Сохраняйте логины, карты и личные данные в своем защищенном хранилище. Bitwarden использует сквозное шифрование, чтобы защитить то, что для вас важно." + "message": "Сохраняйте логины, карты и личную информацию в своем защищенном хранилище. Bitwarden использует сквозное шифрование, чтобы защитить то, что для вас важно." }, "quickLogin": { "message": "Быстрая и простая авторизация" @@ -5320,7 +5337,7 @@ "message": "Упрощение создания аккаунтов" }, "newIdentityNudgeBody": { - "message": "С помощью личностей можно быстро заполнять длинные регистрационные или контактные формы." + "message": "С помощью личной информации можно быстро заполнять длинные регистрационные или контактные формы." }, "newNoteNudgeTitle": { "message": "Храните ваши конфиденциальные данные в безопасности" @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Быстрое создание паролей" + }, + "generatorNudgeBodyOne": { + "message": "Легко создавайте надежные и уникальные пароли, нажатием на кнопку,", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "чтобы обеспечить безопасность ваших логинов.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Легко создавайте надежные и уникальные пароли, нажатием на кнопку 'Сгенерировать пароль', чтобы обеспечить безопасность ваших логинов.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "У вас нет прав для просмотра этой страницы. Попробуйте авторизоваться под другим аккаунтом." } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 308404946b3..eafbf9c584e 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "අද්විතීය හඳුනාගැනීමක් සොයාගත නොහැකි විය." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ ස්වයං සත්කාරක යතුරු සේවාදායකයක් සමඟ SSO භාවිතා කරයි. මෙම සංවිධානයේ සාමාජිකයන් සඳහා ප්රවිෂ්ට වීමට ප්රධාන මුරපදයක් තවදුරටත් අවශ්ය නොවේ.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "සංවිධානය හැරයන්න" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index e8d2993a8df..016f4ca1f5a 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -2766,7 +2766,7 @@ "message": "Nové heslo" }, "sendDisabled": { - "message": "Send zakázaný", + "message": "Send bol odstránený", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nenašiel sa žiadny jedinečný identifikátor." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ používa SSO s vlastným kľúčovým serverom. Na prihlásenie členov tejto organizácie už nie je potrebné hlavné heslo.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hlavné heslo sa už nevyžaduje pre členov tejto organizácie. Nižšie uvedenú doménu potvrďte u správcu organizácie." + }, + "organizationName": { + "message": "Názov organizácie" + }, + "keyConnectorDomain": { + "message": "Doména Key Connectora" }, "leaveOrganization": { "message": "Opustiť organizáciu" @@ -3615,6 +3615,14 @@ "message": "Použite Send na bezpečné zdieľanie zašifrovaných informácii s kýmkoľvek.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send, citlivé informácie bezpečne", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Bezpečne zdieľajte súbory a údaje s kýmkoľvek a na akejkoľvek platforme. Vaše informácie zostanú end-to-end zašifrované a zároveň sa obmedzí ich odhalenie.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Vstup je povinný." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Odomykanie biometrickými údajmi je momentálne z neznámych dôvodov nedostupné." }, + "unlockVault": { + "message": "Odomknite trezor za pár sekúnd" + }, + "unlockVaultDesc": { + "message": "Pre rýchlejší prístup k trezoru si môžete upraviť nastavenia odomknutia a časový limit." + }, + "unlockPinSet": { + "message": "PIN na odomknutie nastavený" + }, "authenticating": { "message": "Overuje sa" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Rýchle vytváranie hesiel" + }, + "generatorNudgeBodyOne": { + "message": "Jednoducho vytvorte silné a jedinečné heslá kliknutím na", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "aby ste mohli ochrániť prihlasovacie údaje.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Jednoducho vytvorte silné a jedinečné heslá kliknutím na tlačidlo Generovať heslo, aby zabezpečili prihlasovacie údaje.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Nemáte oprávnenie na zobrazenie tejto stránky. Skúste sa prihlásiť pomocou iného účtu." } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 65ee31c888c..3f84455be88 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 255b8dfb924..dae425fcfd3 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -1072,7 +1072,7 @@ "description": "Aria label for the view button in notification bar confirmation message" }, "notificationNewItemAria": { - "message": "New Item, opens in new window", + "message": "Нова ставка, отвара се у новом прозору", "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" }, "notificationEditTooltip": { @@ -1093,15 +1093,15 @@ } }, "notificationLoginSaveConfirmation": { - "message": "saved to Bitwarden.", + "message": "сачувано у Bitwarden.", "description": "Shown to user after item is saved." }, "notificationLoginUpdatedConfirmation": { - "message": "updated in Bitwarden.", + "message": "ажурирано у Bitwarden.", "description": "Shown to user after item is updated." }, "selectItemAriaLabel": { - "message": "Select $ITEMTYPE$, $ITEMNAME$", + "message": "Одабрати $ITEMTYPE$, $ITEMNAME$", "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", "placeholders": { "itemType": { @@ -1121,7 +1121,7 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "Откључајте да бисте сачували ову пријаву", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { @@ -1600,10 +1600,10 @@ "message": "Предлог за ауто-попуњавања" }, "autofillSpotlightTitle": { - "message": "Easily find autofill suggestions" + "message": "Једноставно пронађите предлоге за ауто-пуњење" }, "autofillSpotlightDesc": { - "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + "message": "Искључите подешавања ауто-пуњења прегледача, тако да се не сукобљавај са Bitwarden." }, "turnOffBrowserAutofill": { "message": "Turn off $BROWSER$ autofill", @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Није пронађен ниједан јединствени идентификатор." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ користи SSO уз сопствени сервер за кључеве. Главна лозинка за пријаву више није неопходна за чланове ове организације.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Главна лозинка више није потребна за чланове следеће организације. Молимо потврдите домен са администратором организације." + }, + "organizationName": { + "message": "Назив организације" + }, + "keyConnectorDomain": { + "message": "Домен конектора кључа" }, "leaveOrganization": { "message": "Напусти организацију" @@ -3615,6 +3615,14 @@ "message": "Употребите Send да безбедно делите шифроване информације са било ким.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Унос је потребан." }, @@ -4532,31 +4540,31 @@ } }, "downloadBitwarden": { - "message": "Download Bitwarden" + "message": "Преузети Bitwarden" }, "downloadBitwardenOnAllDevices": { - "message": "Download Bitwarden on all devices" + "message": "Преузети Bitwarden на све уређаје" }, "getTheMobileApp": { - "message": "Get the mobile app" + "message": "Узети мобилну апликацију" }, "getTheMobileAppDesc": { - "message": "Access your passwords on the go with the Bitwarden mobile app." + "message": "Приступите лозинци у покрету са Bitwarden мобилном апликацијом." }, "getTheDesktopApp": { - "message": "Get the desktop app" + "message": "Узети desktop апликацију" }, "getTheDesktopAppDesc": { - "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + "message": "Приступите свом сефу без прегледача, а затим поставите откључавање са биометристима да бисте убрзали откључавање и у програму и у додатку." }, "downloadFromBitwardenNow": { - "message": "Download from bitwarden.com now" + "message": "Преузети сада са bitwarden.com" }, "getItOnGooglePlay": { - "message": "Get it on Google Play" + "message": "Набавите на Google Play" }, "downloadOnTheAppStore": { - "message": "Download on the App Store" + "message": "Преузмите са App Store" }, "permanentlyDeleteAttachmentConfirmation": { "message": "Да ли сте сигурни да желите да трајно избришете овај прилог?" @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Биометријско откључавање није доступно из непознатог разлога." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Аутентификација" }, @@ -5269,7 +5286,7 @@ "message": "Сачувајте неограничене лозинке на неограниченим уређајима помоћу Bitwarden мобилних апликација, претраживача и десктоп апликација." }, "nudgeBadgeAria": { - "message": "1 notification" + "message": "1 нотификација" }, "emptyVaultNudgeTitle": { "message": "Увоз постојеће лозинке" @@ -5284,29 +5301,29 @@ "message": "Добродошли у ваш сеф!" }, "hasItemsVaultNudgeBodyOne": { - "message": "Autofill items for the current page" + "message": "Ауто-пуњење предмета за тренутну страницу" }, "hasItemsVaultNudgeBodyTwo": { - "message": "Favorite items for easy access" + "message": "Предмети као омиљен за лак приступ" }, "hasItemsVaultNudgeBodyThree": { - "message": "Search your vault for something else" + "message": "Претражите сеф за нешто друго" }, "newLoginNudgeTitle": { "message": "Уштедите време са ауто-пуњењем" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "Укључите", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "Веб сајт", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "тако да се ова пријава појављује као предлог за ауто-пуњење.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, @@ -5332,16 +5349,33 @@ "message": "Лак SSH приступ" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "Чувајте кључеве и повежите се са SSH агент за брзу, шифровану аутентификацију.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Learn more about SSH agent", + "message": "Сазнајте више о SSH агенту", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { - "message": "You do not have permissions to view this page. Try logging in with a different account." + "message": "Немате дозволе за преглед ове странице. Покушајте да се пријавите са другим налогом." } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 3a063f8f066..f57209122ef 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Ingen unik identifierare hittades." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ använder SSO med en egen nyckelserver. Ett huvudlösenord krävs inte längre för att logga in för medlemmar i denna organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lämna organisation" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Inmatning är obligatoriskt." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index f436c45ab75..4775d1f7af0 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 19430463090..fd9bac62391 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3615,6 +3615,14 @@ "message": "Use Send to securely share encrypted information with anyone.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Input is required." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 700e9b73881..02d7d15b8a2 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Benzersiz tanımlayıcı bulunamadı." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ kendi barındırdığı bir anahtar sunucusuyla SSO kullanıyor. Bu kuruluşun üyelerinin artık ana parola kullanması gerekmiyor.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Kuruluş adı" + }, + "keyConnectorDomain": { + "message": "Key Connector alan adı" }, "leaveOrganization": { "message": "Kuruluştan ayrıl" @@ -3615,6 +3615,14 @@ "message": "Şifrelenmiş bilgileri güvenle paylaşmak için Send'i kullanabilirsiniz.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Hassas bilgileri güvenle paylaşın", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Girdi gerekli." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Kasanızın kilidini saniyeler içinde açın" + }, + "unlockVaultDesc": { + "message": "Kasanıza daha hızlı ulaşmak için kilit açma ve zaman aşımı ayarlarınızı özelleştirebilirsiniz." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Kimlik doğrulanıyor" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "Bu sayfayı görüntüleme izniniz yok. Farklı bir hesapla giriş yapmayı deneyin." } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index b0c78a17106..9406aabe088 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Не знайдено унікальний ідентифікатор." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ використовує SSO з власним сервером ключів. Головний пароль для учасників цієї організації більше не вимагається.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Покинути організацію" @@ -3615,6 +3615,14 @@ "message": "Використовуйте відправлення, щоб безпечно надавати доступ іншим до зашифрованої інформації.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Необхідно ввести дані." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Біометричне розблокування зараз недоступне з невідомої причини." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Аутентифікація" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 14649828aaf..29a7b7109e7 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "Không tìm thấy danh tính duy nhất." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ đang sử dụng SSO với khóa máy chủ tự lưu trữ. Mật khẩu chính không còn cần để đăng nhập cho các thành viên của tổ chức này.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Rời tổ chức" @@ -3615,6 +3615,14 @@ "message": "Sử dụng Gửi để chia sẻ thông tin mã hóa một cách an toàn với bất kỳ ai.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "Trường này là bắt buộc." }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index e0bc34bd47d..8a537be08f2 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -1072,7 +1072,7 @@ "description": "Aria label for the view button in notification bar confirmation message" }, "notificationNewItemAria": { - "message": "新增项目,在新窗口中打开", + "message": "新增项目(在新窗口中打开)", "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" }, "notificationEditTooltip": { @@ -1101,7 +1101,7 @@ "description": "Shown to user after item is updated." }, "selectItemAriaLabel": { - "message": "Select $ITEMTYPE$, $ITEMNAME$", + "message": "选择 $ITEMTYPE$,$ITEMNAME$", "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", "placeholders": { "itemType": { @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "未找到唯一的标识符。" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 使用自托管密钥服务器 SSO。这个组织的成员登录时将不再需要主密码。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "以下组织的成员不再需要主密码。请与您的组织管理员确认下面的域名。" + }, + "organizationName": { + "message": "组织名称" + }, + "keyConnectorDomain": { + "message": "Key Connector 域名" }, "leaveOrganization": { "message": "退出组织" @@ -3615,6 +3615,14 @@ "message": "使用 Send 与任何人安全地分享加密信息。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "安全地发送敏感信息", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "在任何平台上安全地与任何人共享文件和数据。您的信息将在限制曝光的同时保持端到端加密。", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "必须输入内容。" }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "由于某个未知的原因,生物识别解锁当前不可用。" }, + "unlockVault": { + "message": "数秒内解锁您的密码库" + }, + "unlockVaultDesc": { + "message": "您可以自定义解锁和超时设置,以便更快地访问您的密码库。" + }, + "unlockPinSet": { + "message": "解锁 PIN 设置" + }, "authenticating": { "message": "正在验证" }, @@ -5284,24 +5301,24 @@ "message": "欢迎来到您的密码库!" }, "hasItemsVaultNudgeBodyOne": { - "message": "自动填充项目用于当前页面" + "message": "为当前页面自动填充项目" }, "hasItemsVaultNudgeBodyTwo": { - "message": "收藏项目用于轻松访问" + "message": "收藏项目以便快速访问" }, "hasItemsVaultNudgeBodyThree": { - "message": "搜索密码库用于其他" + "message": "在密码库中搜索其他内容" }, "newLoginNudgeTitle": { "message": "使用自动填充节省时间" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "包含", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "网站", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "快速创建密码" + }, + "generatorNudgeBodyOne": { + "message": "一键创建强大且唯一的密码", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "帮助您保持登录安全。", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "一键创建强大且唯一的密码,帮助您保持登录安全。", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "您没有查看此页面的权限。请尝试使用其他账户登录。" } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index e9eab0a726d..9d4eabb1b49 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -3014,14 +3014,14 @@ "copyCustomFieldNameNotUnique": { "message": "找不到唯一識別碼。" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 使用自我裝載金鑰伺服器 SSO。此組織的成員登入時將不再需要主密碼。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "離開組織" @@ -3615,6 +3615,14 @@ "message": "使用 Send 可以與任何人安全地共用加密資訊。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "inputRequired": { "message": "必須輸入內容。" }, @@ -5019,6 +5027,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "基於不明原因,生物辨識解鎖無法使用。" }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "驗證中" }, @@ -5341,6 +5358,23 @@ "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } From 66a8c3ede3d18bde7f0a0c9165165daadea9f231 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 12:00:35 +0200 Subject: [PATCH 143/245] Autosync the updated translations (#14893) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 13 +- apps/web/src/locales/ar/messages.json | 13 +- apps/web/src/locales/az/messages.json | 13 +- apps/web/src/locales/be/messages.json | 13 +- apps/web/src/locales/bg/messages.json | 13 +- apps/web/src/locales/bn/messages.json | 13 +- apps/web/src/locales/bs/messages.json | 13 +- apps/web/src/locales/ca/messages.json | 13 +- apps/web/src/locales/cs/messages.json | 13 +- apps/web/src/locales/cy/messages.json | 13 +- apps/web/src/locales/da/messages.json | 13 +- apps/web/src/locales/de/messages.json | 13 +- apps/web/src/locales/el/messages.json | 13 +- apps/web/src/locales/en_GB/messages.json | 13 +- apps/web/src/locales/en_IN/messages.json | 13 +- apps/web/src/locales/eo/messages.json | 41 +++-- apps/web/src/locales/es/messages.json | 13 +- apps/web/src/locales/et/messages.json | 13 +- apps/web/src/locales/eu/messages.json | 13 +- apps/web/src/locales/fa/messages.json | 199 +++++++++++------------ apps/web/src/locales/fi/messages.json | 13 +- apps/web/src/locales/fil/messages.json | 13 +- apps/web/src/locales/fr/messages.json | 13 +- apps/web/src/locales/gl/messages.json | 13 +- apps/web/src/locales/he/messages.json | 13 +- apps/web/src/locales/hi/messages.json | 13 +- apps/web/src/locales/hr/messages.json | 13 +- apps/web/src/locales/hu/messages.json | 13 +- apps/web/src/locales/id/messages.json | 13 +- apps/web/src/locales/it/messages.json | 13 +- apps/web/src/locales/ja/messages.json | 13 +- apps/web/src/locales/ka/messages.json | 13 +- apps/web/src/locales/km/messages.json | 13 +- apps/web/src/locales/kn/messages.json | 13 +- apps/web/src/locales/ko/messages.json | 13 +- apps/web/src/locales/lv/messages.json | 13 +- apps/web/src/locales/ml/messages.json | 13 +- apps/web/src/locales/mr/messages.json | 13 +- apps/web/src/locales/my/messages.json | 13 +- apps/web/src/locales/nb/messages.json | 13 +- apps/web/src/locales/ne/messages.json | 13 +- apps/web/src/locales/nl/messages.json | 13 +- apps/web/src/locales/nn/messages.json | 13 +- apps/web/src/locales/or/messages.json | 13 +- apps/web/src/locales/pl/messages.json | 13 +- apps/web/src/locales/pt_BR/messages.json | 13 +- apps/web/src/locales/pt_PT/messages.json | 13 +- apps/web/src/locales/ro/messages.json | 13 +- apps/web/src/locales/ru/messages.json | 31 ++-- apps/web/src/locales/si/messages.json | 13 +- apps/web/src/locales/sk/messages.json | 17 +- apps/web/src/locales/sl/messages.json | 13 +- apps/web/src/locales/sr/messages.json | 39 ++--- apps/web/src/locales/sr_CS/messages.json | 13 +- apps/web/src/locales/sv/messages.json | 13 +- apps/web/src/locales/te/messages.json | 13 +- apps/web/src/locales/th/messages.json | 13 +- apps/web/src/locales/tr/messages.json | 13 +- apps/web/src/locales/uk/messages.json | 13 +- apps/web/src/locales/vi/messages.json | 13 +- apps/web/src/locales/zh_CN/messages.json | 27 ++- apps/web/src/locales/zh_TW/messages.json | 13 +- 62 files changed, 448 insertions(+), 634 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 152eb51d7d1..3d2c04f6673 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Ongeldige bevestigingskode" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ gebruik SSO met ’n sleutelbediener op ’n eie gasheer. ’n Hoofwagwoord word nie meer vereis vir aantekening vir lede van hierdie organisasie nie.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Verlaat organisasie" diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index c84858fce08..82907ed4df0 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "رمز التحقق غير صالح" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "مغادرة المؤسسة" diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 3518898335e..121f50f0e00 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Yararsız doğrulama kodu" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$, self-hosted açar serveri ilə SSO istifadə edir. Bu təşkilatın üzvlərinin giriş etməsi üçün artıq ana parol tələb edilməyəcək.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Aşağıdakı təşkilatların üzvləri üçün artıq ana parol tələb olunmur. Lütfən aşağıdakı domeni təşkilatınızın inzibatçısı ilə təsdiqləyin." + }, + "keyConnectorDomain": { + "message": "Key Connector domeni" }, "leaveOrganization": { "message": "Təşkilatı tərk et" diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 789f292aeaf..7409df6baff 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Памылковы праверачны код" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ выкарыстоўвае SSO з уласным серверам ключоў. Асноўны пароль для ўдзельнікаў гэтай арганізацыі больш не патрабуецца.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Выйсці з арганізацыі" diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index b26d6a21578..a8bd9d51e8f 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Грешен код за потвърждаване" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ използва еднократно удостоверяване със собствен сървър за ключове. Членовете на тази организация вече нямат нужда от главна парола за вписване.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "За членовете на следната организация вече не се изисква главна парола. Потвърдете домейна по-долу с администратора на организацията си." + }, + "keyConnectorDomain": { + "message": "Домейн на конектора за ключове" }, "leaveOrganization": { "message": "Напускане на организацията" diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index bb71e44f09d..1419d580837 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index a05e84245e8..31174ebbe5a 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index f24353d69b3..49aadd7951a 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Codi de verificació no vàlid" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ està utilitzant SSO amb un servidor autoallotjat de claus. Ja no es requereix una contrasenya mestra d'inici de sessió per als membres d'aquesta organització.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Abandona l'organització" diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 4ba5a9c3fea..615bfa4f09b 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Neplatný ověřovací kód" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ používá SSO s vlastním serverem s klíči. Hlavní heslo pro členy této organizace již není vyžadováno.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hlavní heslo již není vyžadováno pro členy následující organizace. Potvrďte níže uvedenou doménu u správce Vaší organizace." + }, + "keyConnectorDomain": { + "message": "Doména Key Connectoru" }, "leaveOrganization": { "message": "Opustit organizaci" diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index f91dc727a84..87c852a8869 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index f40daec488f..ee2dc4d570c 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Ugyldig bekræftelseskode" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ anvender SSO med en selv-hostet nøgleserver. Organisationsmedlemmer afkræves ikke længere en hovedadgangskode for at logge ind.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Forlad organisation" diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index f39bb85a082..58b4c568c61 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Ungültiger Verifizierungscode" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ verwendet SSO mit einem selbst gehosteten Schlüsselserver. Ein Master-Passwort ist nicht mehr erforderlich, damit sich Mitglieder dieser Organisation anmelden können.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Für Mitglieder der folgenden Organisation ist kein Master-Passwort mehr erforderlich. Bitte bestätige die folgende Domain bei deinem Organisations-Administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector-Domain" }, "leaveOrganization": { "message": "Organisation verlassen" diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 24de28d39be..3790920f883 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Μη έγκυρος κωδικός επαλήθευσης" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ χρησιμοποιεί SSO με έναν αυτοεξυπηρετητή κλειδιών. Ένας κύριος κωδικός πρόσβασης δεν απαιτείται πλέον για να συνδεθείτε για τα μέλη αυτού του οργανισμού.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Αποχώρηση από τον οργανισμό" diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index d6a1b01f1d5..ab7bf3a1197 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organisation" diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index a0e9f4de149..736cece1de3 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organisation" diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 5b5e1f21582..0a4acdc5739 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -7446,11 +7443,11 @@ "description": "Label for a secret (key/value pair)" }, "serviceAccount": { - "message": "Servila konto", + "message": "Konto ĉe servo", "description": "A machine user which can be used to automate processes and access secrets in the system." }, "serviceAccounts": { - "message": "Servilaj kontoj", + "message": "Kontoj ĉe servo", "description": "The title for the section that deals with service accounts." }, "secrets": { @@ -7474,7 +7471,7 @@ "description": "Title for creating a new secret." }, "newServiceAccount": { - "message": "Nova servila konto", + "message": "Nova konto ĉe servo", "description": "Title for creating a new service account." }, "secretsNoItemsTitle": { @@ -7501,7 +7498,7 @@ "description": "Placeholder text for searching secrets." }, "deleteServiceAccounts": { - "message": "Forigi servilajn kontojn", + "message": "Forigi kontojn ĉe servo", "description": "Title for the action to delete one or multiple service accounts." }, "deleteServiceAccount": { @@ -7509,7 +7506,7 @@ "description": "Title for the action to delete a single service account." }, "viewServiceAccount": { - "message": "Vidi servilan konton", + "message": "Vidi la konton ĉe servo", "description": "Action to view the details of a service account." }, "deleteServiceAccountDialogMessage": { @@ -8170,7 +8167,7 @@ "message": "This user can access Secrets Manager" }, "important": { - "message": "Grave" + "message": "Seriozaĵo:" }, "viewAll": { "message": "Vidi ĉiujn" @@ -8220,7 +8217,7 @@ "message": "Krei projekton" }, "createServiceAccount": { - "message": "Krei servilan konton" + "message": "Krei konton ĉe servo" }, "downloadThe": { "message": "Elŝuti la", @@ -9077,11 +9074,11 @@ "description": "Title to indicate that there are no machine accounts to display." }, "deleteMachineAccounts": { - "message": "Forĵeti maŝinan konton", + "message": "Forĵeti maŝinajn kontojn", "description": "Title for the action to delete one or multiple machine accounts." }, "deleteMachineAccount": { - "message": "Forĵeti maŝinajn kontojn", + "message": "Forĵeti maŝinan konton", "description": "Title for the action to delete a single machine account." }, "viewMachineAccount": { @@ -9580,7 +9577,7 @@ "message": "Vidi sekreton" }, "noClients": { - "message": "Estas neniu kliento por listo" + "message": "Estas neniu kliento por listi" }, "providerBillingEmailHint": { "message": "This email address will receive all invoices pertaining to this provider", @@ -9732,7 +9729,7 @@ "message": "Aldoni al dosierujo" }, "selectFolder": { - "message": "Elekti dosierujo" + "message": "Elekti dosierujon" }, "personalItemTransferWarningSingular": { "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." @@ -10102,7 +10099,7 @@ "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." }, "deleteOrganizationUser": { - "message": "Fo", + "message": "Forigi $NAME$", "placeholders": { "name": { "content": "$1", @@ -10177,7 +10174,7 @@ "message": "This action is not applicable to any of the selected members." }, "deletedSuccessfully": { - "message": "Sukcesis foriĝi" + "message": "Sukcese forigis" }, "freeFamiliesSponsorship": { "message": "Remove Free Bitwarden Families sponsorship" @@ -10246,7 +10243,7 @@ "message": "Claimed" }, "domainStatusUnderVerification": { - "message": "Sub aŭtentigo" + "message": "En konfirmado" }, "claimedDomainsDesc": { "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index a535b45e721..1660429f7f4 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Código de verificación no válido" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ está usando SSO con un servidor de claves autoalojado. Una contraseña maestra ya no es necesaria para iniciar sesión para los miembros de esta organización.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Abandonar organización" diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 9ed4db4b0e3..19608e1fd82 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Vale kinnituskood" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lahku organisatsioonist" diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 29f132852eb..b587e7f1611 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Egiaztatze-kodea ez da baliozkoa" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ ostatatze propioko gako-zerbitzari batekin SSO erabiltzen ari da. Dagoeneko ez da pasahitz nagusirik behar erakunde honetako kideentzat saioa hasteko.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Utzi erakundea" diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 2fbacc8dca2..a7c07192acc 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -1,30 +1,30 @@ { "allApplications": { - "message": "All applications" + "message": "همه‌ برنامه‌ها" }, "appLogoLabel": { - "message": "Bitwarden logo" + "message": "لوگو Bitwarden" }, "criticalApplications": { - "message": "Critical applications" + "message": "برنامه‌های حیاتی" }, "noCriticalAppsAtRisk": { - "message": "No critical applications at risk" + "message": "هیچ برنامه حیاتی در معرض خطر نیست" }, "accessIntelligence": { - "message": "Access Intelligence" + "message": "دسترسی به هوش مصنوعی" }, "riskInsights": { - "message": "Risk Insights" + "message": "دیدگاه‌های خطر" }, "passwordRisk": { - "message": "Password Risk" + "message": "خطر کلمه عبور" }, "reviewAtRiskPasswords": { - "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." + "message": "کلمات عبور در معرض خطر (ضعیف، افشا شده یا تکراری) را در برنامه‌ها بررسی کنید. برنامه‌های حیاتی خود را انتخاب کنید تا اقدامات امنیتی را برای کاربران‌تان اولویت‌بندی کنید و به کلمات عبور در معرض خطر رسیدگی کنید." }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "آخرین به‌روزرسانی داده‌ها: \\$DATE\\$", "placeholders": { "date": { "content": "$1", @@ -33,19 +33,19 @@ } }, "notifiedMembers": { - "message": "Notified members" + "message": "اعضای مطلع شده" }, "revokeMembers": { - "message": "Revoke members" + "message": "حذف اعضا" }, "restoreMembers": { - "message": "Restore members" + "message": "بازیابی اعضا" }, "cannotRestoreAccessError": { - "message": "Cannot restore organization access" + "message": "امکان بازیابی دسترسی به سازمان وجود ندارد" }, "allApplicationsWithCount": { - "message": "All applications ($COUNT$)", + "message": "تمام برنامه‌ها ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -54,10 +54,10 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "ایجاد مورد ورود جدید" }, "criticalApplicationsWithCount": { - "message": "Critical applications ($COUNT$)", + "message": "برنامه‌های حیاتی ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -102,13 +102,13 @@ "message": "Applications marked as critical" }, "application": { - "message": "Application" + "message": "اپلیکیشن" }, "atRiskPasswords": { - "message": "At-risk passwords" + "message": "کلمات عبور در معرض خطر" }, "requestPasswordChange": { - "message": "Request password change" + "message": "درخواست تغییر کلمه عبور" }, "totalPasswords": { "message": "Total passwords" @@ -220,10 +220,10 @@ "message": "یادداشت‌ها" }, "privateNote": { - "message": "Private note" + "message": "یادداشت خصوصی" }, "note": { - "message": "Note" + "message": "یادداشت" }, "customFields": { "message": "فیلدهای سفارشی" @@ -232,19 +232,19 @@ "message": "نام صاحب کارت" }, "loginCredentials": { - "message": "Login credentials" + "message": "اطلاعات ورود" }, "personalDetails": { - "message": "Personal details" + "message": "جزئیات شخصی" }, "identification": { - "message": "Identification" + "message": "شناسایی" }, "contactInfo": { - "message": "Contact info" + "message": "اطلاعات مخاطب" }, "cardDetails": { - "message": "Card details" + "message": "جزئیات کارت" }, "cardBrandDetails": { "message": "$BRAND$ details", @@ -450,17 +450,17 @@ "message": "منطقی" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "کادر انتخاب" }, "cfTypeLinked": { "message": "پیوند شده", "description": "This describes a field that is 'linked' (related) to another field." }, "fieldType": { - "message": "Field type" + "message": "نوع فیلد" }, "fieldLabel": { - "message": "Field label" + "message": "برچسب فیلد" }, "remove": { "message": "حذف" @@ -473,7 +473,7 @@ "description": "This is the folder for uncategorized items" }, "selfOwnershipLabel": { - "message": "You", + "message": "شما", "description": "Used as a label to indicate that the user is the owner of an item." }, "addFolder": { @@ -496,7 +496,7 @@ } }, "newFolder": { - "message": "New folder" + "message": "پوشه جدید" }, "folderName": { "message": "Folder name" @@ -687,7 +687,7 @@ "message": "نام کامل" }, "address": { - "message": "Address" + "message": "نشانی" }, "address1": { "message": "نشانی ۱" @@ -732,7 +732,7 @@ "message": "مشاهده مورد" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "$TYPE$ جدید", "placeholders": { "type": { "content": "$1", @@ -741,7 +741,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "ویرایش $TYPE$", "placeholders": { "type": { "content": "$1", @@ -750,7 +750,7 @@ } }, "viewItemType": { - "message": "View $ITEMTYPE$", + "message": "مشاهده $ITEMTYPE$", "placeholders": { "itemtype": { "content": "$1", @@ -766,10 +766,10 @@ "message": "مورد" }, "itemDetails": { - "message": "Item details" + "message": "جزئیات مورد" }, "itemName": { - "message": "Item name" + "message": "نام مورد" }, "ex": { "message": "مثال.", @@ -795,7 +795,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "کپی موفق بود" }, "copyValue": { "message": "کپی مقدار", @@ -806,11 +806,11 @@ "description": "Copy password to clipboard" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "کپی عبارت عبور", "description": "Copy passphrase to clipboard" }, "passwordCopied": { - "message": "Password copied" + "message": "کلمه عبور کپی شد" }, "copyUsername": { "message": "کپی نام کاربری", @@ -829,7 +829,7 @@ "description": "Copy URI to clipboard" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "کپی $FIELD$", "placeholders": { "field": { "content": "$1", @@ -838,34 +838,34 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "کپی وب‌سایت" }, "copyNotes": { - "message": "Copy notes" + "message": "کپی یادداشت‌ها" }, "copyAddress": { - "message": "Copy address" + "message": "کپی نشانی" }, "copyPhone": { - "message": "Copy phone" + "message": "کپی تلفن" }, "copyEmail": { - "message": "Copy email" + "message": "کپی ایمیل" }, "copyCompany": { - "message": "Copy company" + "message": "کپی شرکت" }, "copySSN": { - "message": "Copy Social Security number" + "message": "کپی شماره کد ملی" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "کپی شماره گذرنامه" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "کپی شماره گواهینامه" }, "copyName": { - "message": "Copy name" + "message": "کپی نام" }, "me": { "message": "من" @@ -944,7 +944,7 @@ } }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "موارد به $ORGNAME$ منتقل شدند", "placeholders": { "orgname": { "content": "$1", @@ -953,7 +953,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "مورد به $ORGNAME$ منتقل شد", "placeholders": { "orgname": { "content": "$1", @@ -1019,16 +1019,16 @@ "message": "نشست ورود شما منقضی شده است." }, "restartRegistration": { - "message": "Restart registration" + "message": "ثبت‌نام را دوباره آغاز کنید" }, "expiredLink": { - "message": "Expired link" + "message": "پیوند منقضی شد" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "لطفاً ثبت نام را مجدداً شروع کنید یا دوباره وارد شوید." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "ممکن است قبلاً حساب کاربری داشته باشید" }, "logOutConfirmation": { "message": "آیا مطمئنید که می‌خواهید خارج شوید؟" @@ -1046,7 +1046,7 @@ "message": "خیر" }, "location": { - "message": "Location" + "message": "موقعیت" }, "loginOrCreateNewAccount": { "message": "وارد شوید یا یک حساب کاربری بسازید تا به گاوصندوق امن‌تان دسترسی یابید." @@ -1058,28 +1058,28 @@ "message": "ورود به سیستم با دستگاه باید در تنظیمات برنامه‌ی Bitwarden تنظیم شود. به گزینه دیگری نیاز دارید؟" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "به گزینه دیگری نیاز دارید؟" }, "loginWithMasterPassword": { "message": "با کلمه عبور اصلی وارد شوید" }, "readingPasskeyLoading": { - "message": "Reading passkey..." + "message": "خواندن کلید عبور..." }, "readingPasskeyLoadingInfo": { - "message": "Keep this window open and follow prompts from your browser." + "message": "این پنجره را باز نگه دارید و دستورهای مرورگر خود را دنبال کنید." }, "useADifferentLogInMethod": { "message": "Use a different log in method" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "با کلید عبور وارد شوید" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "استفاده از ورود تک مرحله‌ای" }, "welcomeBack": { - "message": "Welcome back" + "message": "خوش آمدید" }, "invalidPasskeyPleaseTryAgain": { "message": "Invalid Passkey. Please try again." @@ -1163,13 +1163,13 @@ "message": "ایجاد حساب کاربری" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "در Bitwarden تازه وارد هستید؟" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "تنظیم کلمه عبور قوی" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "ایجاد حساب کاربری خود را با تنظیم کلمه عبور تکمیل کنید" }, "newAroundHere": { "message": "اینجا تازه واردی؟" @@ -1181,22 +1181,22 @@ "message": "ورود" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "وارد Bitwarden شوید" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "کدی را که به ایمیل شما ارسال شده وارد کنید" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "کد سامانه تأیید کننده را وارد نمایید" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "برای احراز هویت، کلید YubiKey خود را فشار دهید" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "پایان زمان احراز هویت" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "نشست احراز هویت منقضی شد. لطفاً فرایند ورود را دوباره شروع کنید." }, "verifyYourIdentity": { "message": "Verify your Identity" @@ -1736,25 +1736,25 @@ "message": "تاریخچه کلمه عبور" }, "generatorHistory": { - "message": "Generator history" + "message": "تاریخچه تولید کننده" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "پاک کردن تاریخچه تولید کننده" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "اگر ادامه دهید، تمام ورودی‌ها به‌طور دائمی از تاریخچه تولید کننده حذف خواهند شد. آیا مطمئن هستید که می‌خواهید ادامه دهید؟" }, "noPasswordsInList": { "message": "هیچ کلمه عبوری برای فهرست کردن وجود ندارد." }, "clearHistory": { - "message": "Clear history" + "message": "پاک کردن تاریخچه" }, "nothingToShow": { - "message": "Nothing to show" + "message": "چیزی برای نشان دادن موجود نیست" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "شما اخیراً چیزی تولید نکرده‌اید" }, "clear": { "message": "پاک کردن", @@ -1794,10 +1794,10 @@ "message": "لطفاً دوباره وارد شوید." }, "currentSession": { - "message": "Current session" + "message": "نشست کنونی" }, "requestPending": { - "message": "Request pending" + "message": "درخواست در حال انتظار است" }, "logBackInOthersToo": { "message": "لطفاً دوباره وارد شوید. اگر از سایر برنامه‌های Bitwarden استفاده می‌کنید، از سیستم خارج شوید و دوباره به آن‌ها وارد شوید." @@ -1961,11 +1961,11 @@ "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { - "message": "new item", + "message": "مورد جدید", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLoginLink": { - "message": "new login", + "message": "ورود جدید", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new login instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsPartTwoNoOrgs": { @@ -2016,7 +2016,7 @@ "message": "خطا در رمزگشایی پرونده‌ی درون ریزی شده. کلید رمزگذاری شما با کلید رمزگذاری استفاده شده برای درون ریزی داده‌ها مطابقت ندارد." }, "destination": { - "message": "Destination" + "message": "مقصد" }, "learnAboutImportOptions": { "message": "درباره گزینه‌های برون ریزی خود بیاموزید" @@ -2201,16 +2201,16 @@ "message": "مدیریت" }, "manageCollection": { - "message": "Manage collection" + "message": "مدیریت مجموعه" }, "viewItems": { - "message": "View items" + "message": "مشاهده موارد" }, "viewItemsHidePass": { "message": "View items, hidden passwords" }, "editItems": { - "message": "Edit items" + "message": "ویرایش موارد" }, "editItemsHidePass": { "message": "Edit items, hidden passwords" @@ -2222,7 +2222,7 @@ "message": "لغو دسترسی" }, "revoke": { - "message": "Revoke" + "message": "لغو" }, "twoStepLoginProviderEnabled": { "message": "این ارائه دهنده ورود به سیستم دو مرحله ای در حساب شما فعال است." @@ -2237,7 +2237,7 @@ "message": "," }, "twoStepAuthenticatorInstructionInfix2": { - "message": "or" + "message": "یا" }, "twoStepAuthenticatorInstructionSuffix": { "message": "." @@ -2255,10 +2255,10 @@ "message": "You are leaving Bitwarden and launching an external website in a new window." }, "twoStepContinueToBitwardenUrlTitle": { - "message": "Continue to bitwarden.com?" + "message": "به bitwarden.com ادامه می‌دهید؟" }, "twoStepContinueToBitwardenUrlDesc": { - "message": "Bitwarden Authenticator allows you to store authenticator keys and generate TOTP codes for 2-step verification flows. Learn more on the bitwarden.com website." + "message": "احراز هویت کننده Bitwarden به شما امکان می‌دهد کلیدهای احراز هویت را ذخیره کرده و کدهای TOTP را برای فرآیندهای تأیید دومرحله‌ای تولید کنید. برای اطلاعات بیشتر به وب‌سایت bitwarden.com مراجعه کنید." }, "twoStepAuthenticatorScanCodeV2": { "message": "Scan the QR code below with your authenticator app or enter the key." @@ -2270,7 +2270,7 @@ "message": "کلید" }, "twoStepAuthenticatorEnterCodeV2": { - "message": "Verification code" + "message": "کد تأیید" }, "twoStepAuthenticatorReaddDesc": { "message": "در صورت نیاز به افزودن آن به دستگاه دیگر، کد QR (یا کلید) مورد نیاز برنامه احراز هویت شما در زیر آمده است." @@ -2351,7 +2351,7 @@ "message": "اطلاعات برنامه Bitwarden را از پنل مدیریت Duo خود وارد کنید." }, "twoFactorDuoClientId": { - "message": "Client Id" + "message": "شناسه کاربر" }, "twoFactorDuoClientSecret": { "message": "Client Secret" @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "کد تأیید نامعتبر است" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ در حال استفاده از SSO با یک سرور کلید خود میزبان است. برای ورود اعضای این سازمان دیگر نیازی به کلمه عبور اصلی نیست.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "ترک سازمان" diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 83efcc197fc..ccbcf4f7cc3 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Virheellinen todennuskoodi" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ käyttää kertakirjautumista itse ylläpidetyllä avainpalvelimella. Organisaation jäsenet eivät enää tarvitse pääsalasanaa kirjautumiseen.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Eroa organisaatiosta" diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 15e0793d6ce..97e22e18045 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Maling verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ ay gumagamit ng SSO na may sariling-hosted na key server. Walang kinakailangang master password para mag-log in para sa mga miyembro ng organisasyon na ito.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Umalis sa organisasyon" diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 39fe5b3c1cf..d881ecfefd3 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Code de vérification invalide" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ utilise le SSO avec un serveur de clés auto-hébergé. Un mot de passe principal n'est plus nécessaire aux membres de cette organisation pour se connecter.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Un mot de passe maître n'est plus requis pour les membres de l'organisation suivante. Veuillez confirmer le domaine ci-dessous avec l'administrateur de votre organisation." + }, + "keyConnectorDomain": { + "message": "Domaine Key Connector" }, "leaveOrganization": { "message": "Quitter l'organisation" diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index eae36865876..707f9752ab6 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 286276f96e5..6dd2d9243b1 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "קוד אימות שגוי" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ משתמש/ת ב־SSO עם שרת מפתחות באירוח עצמי. סיסמה ראשית כבר לא נדרשת כדי להיכנס עבור חברים של ארגון זה.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "עזוב ארגון" diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index dd2073a924f..22a31e5df70 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 4f1c4c981b2..3c37dc9cedf 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Nevažeći kôd za provjeru" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ koristi jedinstvenu prijavu SSO s vlastitim poslužiteljem. Članovima organizacije glavna lozinka više nije potrebna.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Napusti organizaciju" diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 12fd95bbbaa..33ea6c3d9cd 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Érvénytelen ellenőrző kód" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ jelenleg saját tárolású aláíráskulcsú SSO szervert használ. A mesterjelszó a továbbiakban nem szükséges a szervezeti tagsági bejelentkezéshez.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A következő szervezet tagjai számára már nincs szükség mesterjelszóra. Erősítsük meg az alábbi tartományt a szervezet adminisztrátorával." + }, + "keyConnectorDomain": { + "message": "Key Connector tartomány" }, "leaveOrganization": { "message": "Szervezet elhagyása" diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 3706fb73f63..379a6424333 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Kode verifikasi tidak valid" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Tinggalkan Organisasi" diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index a30c1f81f93..8019ac7a750 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Codice di verifica non valido" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ sta usando SSO con un server self-hosted. Una password principale non è più necessaria per accedere per i membri di questa organizzazione.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lascia organizzazione" diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 1bb8bf364b0..2de545cd27a 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "認証コードが間違っています" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ は自己ホストの鍵サーバで SSO を使用しています。この組織のメンバーのログインにマスターパスワードは必要ありません。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "組織から脱退する" diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 72e9066806c..863f8d11dea 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 38740c78cba..27a160a9a3c 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 2758b022b64..98c317d377f 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index ee089584adf..2340ae849fa 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "유효하지 않은 확인 코드" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "조직 나가기" diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 61ff8651c72..27818e4e3b9 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Nederīgs apliecinājuma kods" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ izmanto vienoto pieteikšanos ar pašmitinātu atslēgu serveri. Tās dalībniekiem vairs nav nepieciešama galvenā parole, lai pieteiktos.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Galvenā parole vairs nav nepieciešama turpmāk minētās apvienības dalībniekiem. Lūgums saskaņot zemāk esošo domēnu ar savas apvienības pārvaldītāju." + }, + "keyConnectorDomain": { + "message": "Key Connector domēns" }, "leaveOrganization": { "message": "Pamest apvienību" diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index dd4ca0b94cf..54a837b83e1 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 38740c78cba..27a160a9a3c 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 38740c78cba..27a160a9a3c 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 93df01f3440..b2068d1d939 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Ugyldig bekreftelseskode" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ bruker SSO med en selvdrevet nøkkelserver. Et hovedpassord er ikke lenger nødvendig for å logge inn for medlemmer av denne organisasjonen.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Forlat organisasjonen" diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 215ebaf1849..c8c0c90dcde 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 9d192639377..d92b9707fc9 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Ongeldige verificatiecode" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ gebruikt SSO met een zelf gehoste sleutelserver. Leden van deze organisatie kunnen inloggen zonder hoofdwachtwoord.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Voor leden van de volgende organisatie is een hoofdwachtwoord niet langer nodig. Bevestig het domein hieronder met de beheerder van je organisatie." + }, + "keyConnectorDomain": { + "message": "Key Connector-domein" }, "leaveOrganization": { "message": "Organisatie verlaten" diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 3c03b2e7cb2..619c1776cb6 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 38740c78cba..27a160a9a3c 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index ab471e892ef..de9caf9bbce 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Kod weryfikacyjny jest nieprawidłowy" }, - "convertOrganizationEncryptionDesc": { - "message": "Organizacja $ORGANIZATION$ używa jednokrotnego logowania SSO z własnym serwerem kluczy. Użytkownicy nie muszą logować się za pomocą hasła głównego.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hasło główne nie jest już wymagane dla członków następującej organizacji. Proszę potwierdzić poniższą domenę u administratora organizacji." + }, + "keyConnectorDomain": { + "message": "Domena Key Connector'a" }, "leaveOrganization": { "message": "Opuść organizację" diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index b3f6dd37a8c..04403ade2d9 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Código de verificação inválido" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ está usando SSO com um servidor de chaves auto-hospedado. Não é mais necessária uma senha mestra para os membros desta organização entrarem.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Sair da Organização" diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 5f1e02c82e3..dc66923f629 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Código de verificação inválido" }, - "convertOrganizationEncryptionDesc": { - "message": "A $ORGANIZATION$ está a utilizar o SSO com um servidor de chaves auto-hospedado. Já não é necessária uma palavra-passe mestra para iniciar sessão para os membros desta organização.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Já não é necessária uma palavra-passe mestra para os membros da seguinte organização. Por favor, confirme o domínio abaixo com o administrador da sua organização." + }, + "keyConnectorDomain": { + "message": "Domínio do Key Connector" }, "leaveOrganization": { "message": "Sair da organização" diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 64eb05dad22..632733642b1 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Cod de verificare nevalid" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ folosește SSO cu un server de chei auto-găzduit. Membrii acestei organizații nu mai au nevoie de o parolă principală pentru autentificare.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Părăsire organizație" diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index aaccc3b2145..c30371f7d86 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -141,13 +141,13 @@ "message": "Эти пользователи входят в приложения со слабыми, скомпрометированными или повторно используемыми паролями." }, "atRiskMembersDescriptionNone": { - "message": "These are no members logging into applications with weak, exposed, or reused passwords." + "message": "Нет пользователей, входящих в приложения со слабыми, скомпрометированными или повторно используемыми паролями." }, "atRiskApplicationsDescription": { "message": "Эти приложения имеют слабые, скомпрометированные или повторно используемые пароли." }, "atRiskApplicationsDescriptionNone": { - "message": "These are no applications with weak, exposed, or reused passwords." + "message": "Нет приложений со слабыми, скомпрометированными или повторно используемыми паролями." }, "atRiskMembersDescriptionWithApp": { "message": "Эти пользователи входят в $APPNAME$ со слабыми, скомпрометированными или повторно используемыми паролями.", @@ -159,7 +159,7 @@ } }, "atRiskMembersDescriptionWithAppNone": { - "message": "There are no at risk members for $APPNAME$.", + "message": "Нет пользователей, подверженных риску для $APPNAME$.", "placeholders": { "appname": { "content": "$1", @@ -235,7 +235,7 @@ "message": "Данные для авторизации" }, "personalDetails": { - "message": "Личные данные" + "message": "Личная информация" }, "identification": { "message": "Идентификация" @@ -333,7 +333,7 @@ "message": "Код безопасности / CVV" }, "identityName": { - "message": "Название личности" + "message": "Название личной информации" }, "company": { "message": "Компания" @@ -610,7 +610,7 @@ "description": "Search Card type" }, "searchIdentity": { - "message": "Поиск личностей", + "message": "Поиск личной информации", "description": "Search Identity type" }, "searchSecureNote": { @@ -2096,7 +2096,7 @@ "message": "Правила домена" }, "domainRulesDesc": { - "message": "Если у вас есть тот же логин на нескольких разных доменах сайта, вы можете отметить сайт как \"эквивалентный\". \"Глобальные\" - это домены, созданные для вас Bitwarden." + "message": "Если у вас есть такой же логин на нескольких разных доменах сайта, вы можете отметить сайт как \"эквивалентный\". \"Глобальные\" домены это домены, созданные Bitwarden для вас." }, "globalEqDomains": { "message": "Глобальные эквивалентные домены" @@ -4549,7 +4549,7 @@ "message": "После обновления ключа шифрования необходимо выйти из всех приложений Bitwarden, которые вы используете (например, из мобильного приложения или расширения браузера). Если этого не сделать, могут повредиться данные (так как при выходе и последующем входе загружается ваш новый ключ шифрования). Мы попытаемся автоматически осуществить завершение ваших сессий, однако это может произойти с задержкой." }, "updateEncryptionKeyAccountExportWarning": { - "message": "Any account restricted exports you have saved will become invalid." + "message": "Все сохраненные экспорты, затронутые ограничениями аккаунта, будут аннулированы." }, "subscription": { "message": "Подписка" @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Неверный код подтверждения" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ использует SSO с собственным сервером ключей. Для авторизации пользователям этой организации больше не требуется мастер-пароль.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Мастер-пароль больше не требуется для членов следующей организации. Пожалуйста, подтвердите указанный ниже домен у администратора вашей организации." + }, + "keyConnectorDomain": { + "message": "Домен соединителя ключей" }, "leaveOrganization": { "message": "Покинуть организацию" @@ -10584,7 +10581,7 @@ "message": "Упрощение создания аккаунтов" }, "newIdentityNudgeBody": { - "message": "С помощью личностей можно быстро заполнять длинные регистрационные или контактные формы." + "message": "С помощью личной информации можно быстро заполнять длинные регистрационные или контактные формы." }, "newNoteNudgeTitle": { "message": "Храните ваши конфиденциальные данные в безопасности" diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 2d270a54731..f75fd53c209 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 6bbf765ba78..e785bd3749b 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -159,7 +159,7 @@ } }, "atRiskMembersDescriptionWithAppNone": { - "message": "There are no at risk members for $APPNAME$.", + "message": "Pre $APPNAME$ neexistujú žiadni ohrození členovia.", "placeholders": { "appname": { "content": "$1", @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Neplatný verifikačný kód" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ používa SSO s vlastným kľúčovým serverom. Na prihlásenie členov tejto organizácie už nie je potrebné hlavné heslo.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Opustiť organizáciu" @@ -10612,6 +10609,6 @@ "message": "Platba prostredníctvom bankového účtu je dostupná len pre zákazníkov v Spojených Štátoch. Budete musieť overiť svoj bankový účet. V priebehu nasledujúcich 1-2 pracovných dní vykonáme mikro vklad. Na overenie bankového účtu zadajte kód popisu výpisu z tohto vkladu na fakturačnej stránke poskytovateľa. Neoverenie bankového účtu bude mať za následok neuskutočnenie platby a pozastavenie vášho predplatného." }, "clickPayWithPayPal": { - "message": "Please click the Pay with PayPal button to add your payment method." + "message": "Pre pridanie platobnej metódy kliknite prosím na Zaplatiť cez PayPal." } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 06c5339ad98..5a43561bf5a 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 2ca7e72e227..e54b6d148ca 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -99,7 +99,7 @@ "message": "Означите апликацију као критичну" }, "applicationsMarkedAsCriticalSuccess": { - "message": "Applications marked as critical" + "message": "Апликације означене као критичне" }, "application": { "message": "Апликација" @@ -141,13 +141,13 @@ "message": "Ови чланови се пријављују у апликације са слабим, откривеним или поново коришћеним лозинкама." }, "atRiskMembersDescriptionNone": { - "message": "These are no members logging into applications with weak, exposed, or reused passwords." + "message": "Нема чланова пријављена у апликације са слабим, откривеним или поново коришћеним лозинкама." }, "atRiskApplicationsDescription": { "message": "Ове апликације имају слабу, проваљену или често коришћену лозинку." }, "atRiskApplicationsDescriptionNone": { - "message": "These are no applications with weak, exposed, or reused passwords." + "message": "Ове апликације немају слабу, проваљену или често коришћену лозинку." }, "atRiskMembersDescriptionWithApp": { "message": "Ови чланови се пријављују у $APPNAME$ са слабим, откривеним или поново коришћеним лозинкама.", @@ -4549,7 +4549,7 @@ "message": "Након ажурирања кључа за шифровање, мораћете да се одјавите и вратите у све Bitwarden апликације које тренутно користите (као што су мобилна апликација или додаци прегледача). Ако се не одјавите и поново пријавите (чиме се преузима ваш нови кључ за шифровање), може доћи до оштећења података. Покушаћемо аутоматски да се одјавимо, али може доћи до одлагања." }, "updateEncryptionKeyAccountExportWarning": { - "message": "Any account restricted exports you have saved will become invalid." + "message": "Сваки рачун са ограничен извоз који сте сачували постаће неважећи." }, "subscription": { "message": "Претплата" @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Неисправан верификациони код" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Главна лозинка више није потребна за чланове следеће организације. Молимо потврдите домен са администратором организације." + }, + "keyConnectorDomain": { + "message": "Домен конектора кључа" }, "leaveOrganization": { "message": "Напусти организацију" @@ -9429,19 +9426,19 @@ "message": "Управљајте наплатом из Provider Portal" }, "continueSettingUp": { - "message": "Continue setting up Bitwarden" + "message": "Наставити са подешавањем Bitwarden-а" }, "continueSettingUpFreeTrial": { "message": "Наставите са подешавањем бесплатне пробне верзије Bitwarden-а" }, "continueSettingUpPasswordManager": { - "message": "Continue setting up Bitwarden Password Manager" + "message": "Наставите са подешавањем Bitwarden менаџер лозинки" }, "continueSettingUpFreeTrialPasswordManager": { "message": "Наставите са подешавањем бесплатне пробне верзије Bitwarden менаџер лозинки" }, "continueSettingUpSecretsManager": { - "message": "Continue setting up Bitwarden Secrets Manager" + "message": "Наставите са подешавањем Bitwarden Secrets Manager" }, "continueSettingUpFreeTrialSecretsManager": { "message": "Наставите са подешавањем бесплатне пробне верзије Bitwarden Secrets Manager" @@ -10560,17 +10557,17 @@ "message": "Уштедите време са ауто-пуњењем" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "Укључите", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "Веб сајт", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "тако да се ова пријава појављује као предлог за ауто-пуњење.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, @@ -10596,12 +10593,12 @@ "message": "Лак SSH приступ" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "Чувајте кључеве и повежите се са SSH агент за брзу, шифровану аутентификацију.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Learn more about SSH agent", + "message": "Сазнајте више о SSH агенту", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, @@ -10612,6 +10609,6 @@ "message": "Плаћање са банковним рачуном доступно је само купцима у Сједињеним Државама. Од вас ће бити потребно да проверите свој банковни рачун. Направит ћемо микро депозит у наредних 1-2 радна дана. Унесите кôд за дескриптор изјаве са овог депозита на претплатничкој страници провајдера да бисте потврдили банковни рачун. Неуспех у верификацији банковног рачуна резултираће пропуштеним плаћањем и претплатом је суспендовано." }, "clickPayWithPayPal": { - "message": "Please click the Pay with PayPal button to add your payment method." + "message": "Кликните на Pay with PayPal да бисте додали начин лпаћања." } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 6b10fd30018..95b9348aa04 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 1ca059f95ec..3bbfc4577ea 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Ogiltig verifieringskod" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lämna organisation" diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 38740c78cba..27a160a9a3c 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 393442e42e5..7651ae5bc06 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index c59f33bba45..7e6f3e6ab4e 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Geçersiz doğrulama kodu" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ kendi barındırdığı bir anahtar sunucusuyla SSO kullanıyor. Bu kuruluşun üyelerinin artık ana parola kullanması gerekmiyor.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector alan adı" }, "leaveOrganization": { "message": "Kuruluştan ayrıl" diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index 288e8e840a0..a899bde3d04 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Недійсний код підтвердження" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ використовує SSO з власним сервером ключів. Головний пароль для учасників цієї організації більше не вимагається.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Покинути організацію" diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index cee1bf8ebbd..78a536991a7 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 9805eaac5fd..eb186f11360 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -1870,7 +1870,7 @@ "message": "取消会话授权" }, "deauthorizeSessionsDesc": { - "message": "您是否担心自己的账户在其他设备上登录过?请按照以下步骤取消对之前使用过的所有计算机或设备的授权。如果您以前使用过公共计算机或不小心曾将密码保存在不属于您的设备上,则建议执行此安全步骤。此步骤还将清除所有以前记住的两步登录会话。" + "message": "您是否担心自己的账户在其他设备上登录过?继续下面的操作以取消对之前使用过的所有计算机或设备的授权。如果您以前使用过公共计算机或不小心曾将密码保存在不属于您的设备上,则建议执行此安全步骤。此步骤还将清除所有以前记住的两步登录会话。" }, "deauthorizeSessionsWarning": { "message": "继续操作还将使您退出当前会话,并要求您重新登录。如果有设置两步登录,也需要重新验证。其他设备上的活动会话可能会继续保持活动状态长达一小时。" @@ -1885,10 +1885,10 @@ "message": "启用新设备登录保护" }, "turnOffNewDeviceLoginProtectionModalDesc": { - "message": "继续下面的操作以停用 Bitwarden 在您从新设备登录时发送验证电子邮件的功能。" + "message": "继续下面的操作以停用从新设备登录 Bitwarden 时发送验证电子邮件的功能。" }, "turnOnNewDeviceLoginProtectionModalDesc": { - "message": "继续下面的操作以启用 Bitwarden 在您从新设备登录时发送验证电子邮件的功能。" + "message": "继续下面的操作以启用从新设备登录 Bitwarden 时发送验证电子邮件的功能。" }, "turnOffNewDeviceLoginProtectionWarning": { "message": "停用新设备登录保护后,任何拥有您的主密码的人都可以从任何设备访问您的账户。要在没有验证电子邮件的情况下保护您的账户,请设置两步登录。" @@ -1918,10 +1918,10 @@ "message": "密码库被提供商访问。" }, "purgeVaultDesc": { - "message": "接下来的操作会删除密码库中的所有项目和文件夹。属于组织的共享项目将不会被删除。" + "message": "继续下面的操作以删除密码库中的所有项目和文件夹。属于组织的共享项目将不会被删除。" }, "purgeOrgVaultDesc": { - "message": "接下来的操作会删除组织密码库中的所有项目。" + "message": "继续下面的操作以删除组织密码库中的所有项目。" }, "purgeVaultWarning": { "message": "清空密码库是永久性操作,无法撤销!" @@ -1933,7 +1933,7 @@ "message": "删除账户" }, "deleteAccountDesc": { - "message": "接下来的操作会删除您的账户和所有密码库数据。" + "message": "继续下面的操作以删除您的账户和所有密码库数据。" }, "deleteAccountWarning": { "message": "删除账户是永久性操作,无法撤销!" @@ -4549,7 +4549,7 @@ "message": "更新加密密钥后,您需要注销所有当前使用的 Bitwarden 应用程序(例如移动 App 或浏览器扩展)然后重新登录。注销或者重新登录(这将下载新的加密密钥)失败可能会导致数据损坏。我们会尝试自动为您注销,但可能会有所延迟。" }, "updateEncryptionKeyAccountExportWarning": { - "message": "您已保存的所有账户限制的导出文件将失效。" + "message": "所有您已保存的账户限制的导出文件将失效。" }, "subscription": { "message": "订阅" @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "无效的验证码" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 使用自托管密钥服务器 SSO。这个组织的成员登录时将不再需要主密码。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "以下组织的成员不再需要主密码。请与您的组织管理员确认下面的域名。" + }, + "keyConnectorDomain": { + "message": "Key Connector 域名" }, "leaveOrganization": { "message": "退出组织" diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 57dd4c8631f..2f5567308d2 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -6476,14 +6476,11 @@ "invalidVerificationCode": { "message": "無效的驗證碼" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 使用自我裝載金鑰伺服器 SSO。此組織的成員登入時將不再需要主密碼。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "離開組織" From eb3e14b02211210daf1c77c7d26b52ff7dfc7527 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 12:29:05 +0200 Subject: [PATCH 144/245] Autosync the updated translations (#14891) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/af/messages.json | 25 +- apps/desktop/src/locales/ar/messages.json | 25 +- apps/desktop/src/locales/az/messages.json | 25 +- apps/desktop/src/locales/be/messages.json | 25 +- apps/desktop/src/locales/bg/messages.json | 25 +- apps/desktop/src/locales/bn/messages.json | 25 +- apps/desktop/src/locales/bs/messages.json | 25 +- apps/desktop/src/locales/ca/messages.json | 25 +- apps/desktop/src/locales/cs/messages.json | 25 +- apps/desktop/src/locales/cy/messages.json | 25 +- apps/desktop/src/locales/da/messages.json | 25 +- apps/desktop/src/locales/de/messages.json | 25 +- apps/desktop/src/locales/el/messages.json | 25 +- apps/desktop/src/locales/en_GB/messages.json | 25 +- apps/desktop/src/locales/en_IN/messages.json | 25 +- apps/desktop/src/locales/eo/messages.json | 25 +- apps/desktop/src/locales/es/messages.json | 87 ++- apps/desktop/src/locales/et/messages.json | 25 +- apps/desktop/src/locales/eu/messages.json | 25 +- apps/desktop/src/locales/fa/messages.json | 781 ++++++++++--------- apps/desktop/src/locales/fi/messages.json | 69 +- apps/desktop/src/locales/fil/messages.json | 25 +- apps/desktop/src/locales/fr/messages.json | 25 +- apps/desktop/src/locales/gl/messages.json | 25 +- apps/desktop/src/locales/he/messages.json | 25 +- apps/desktop/src/locales/hi/messages.json | 25 +- apps/desktop/src/locales/hr/messages.json | 25 +- apps/desktop/src/locales/hu/messages.json | 25 +- apps/desktop/src/locales/id/messages.json | 25 +- apps/desktop/src/locales/it/messages.json | 25 +- apps/desktop/src/locales/ja/messages.json | 25 +- apps/desktop/src/locales/ka/messages.json | 25 +- apps/desktop/src/locales/km/messages.json | 25 +- apps/desktop/src/locales/kn/messages.json | 25 +- apps/desktop/src/locales/ko/messages.json | 25 +- apps/desktop/src/locales/lt/messages.json | 25 +- apps/desktop/src/locales/lv/messages.json | 25 +- apps/desktop/src/locales/me/messages.json | 25 +- apps/desktop/src/locales/ml/messages.json | 25 +- apps/desktop/src/locales/mr/messages.json | 25 +- apps/desktop/src/locales/my/messages.json | 25 +- apps/desktop/src/locales/nb/messages.json | 25 +- apps/desktop/src/locales/ne/messages.json | 25 +- apps/desktop/src/locales/nl/messages.json | 25 +- apps/desktop/src/locales/nn/messages.json | 25 +- apps/desktop/src/locales/or/messages.json | 25 +- apps/desktop/src/locales/pl/messages.json | 25 +- apps/desktop/src/locales/pt_BR/messages.json | 25 +- apps/desktop/src/locales/pt_PT/messages.json | 25 +- apps/desktop/src/locales/ro/messages.json | 25 +- apps/desktop/src/locales/ru/messages.json | 29 +- apps/desktop/src/locales/si/messages.json | 25 +- apps/desktop/src/locales/sk/messages.json | 25 +- apps/desktop/src/locales/sl/messages.json | 25 +- apps/desktop/src/locales/sr/messages.json | 71 +- apps/desktop/src/locales/sv/messages.json | 25 +- apps/desktop/src/locales/te/messages.json | 25 +- apps/desktop/src/locales/th/messages.json | 25 +- apps/desktop/src/locales/tr/messages.json | 25 +- apps/desktop/src/locales/uk/messages.json | 25 +- apps/desktop/src/locales/vi/messages.json | 167 ++-- apps/desktop/src/locales/zh_CN/messages.json | 29 +- apps/desktop/src/locales/zh_TW/messages.json | 25 +- 63 files changed, 1600 insertions(+), 1033 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 479f8f47c1f..28cff8cae13 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Hoofwagwoord is verwyder" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ gebruik SSO met ’n sleutelbediener op ’n eie gasheer. ’n Hoofwagwoord word nie meer vereis vir aantekening vir lede van hierdie organisasie nie.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Verlaat organisasie" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 510c1aa918a..d9dfb7cdff5 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "تمت إزالة كلمة المرور الرئيسية." }, - "convertOrganizationEncryptionDesc": { - "message": "يستخدم $ORGANIZATION$ SSO مع خادم مفتاح الاستضافة الذاتية. لم تعد هناك حاجة إلى كلمة مرور رئيسية لتسجيل الدخول لأعضاء هذه المؤسسة.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "مغادرة المؤسسة" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 459458bdb2a..5c1f49cbfff 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Ana parol silindi." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$, self-hosted açar serveri ilə SSO istifadə edir. Bu təşkilatın üzvlərinin giriş etməsi üçün artıq ana parol tələb edilməyəcək.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Aşağıdakı təşkilatların üzvləri üçün artıq ana parol tələb olunmur. Lütfən aşağıdakı domeni təşkilatınızın inzibatçısı ilə təsdiqləyin." + }, + "organizationName": { + "message": "Təşkilat adı" + }, + "keyConnectorDomain": { + "message": "Key Connector domeni" }, "leaveOrganization": { "message": "Təşkilatı tərk et" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Riskli parolları dəyişdir" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Daşı" }, diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 4e2441ac168..9284f683e58 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Асноўны пароль выдалены." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ выкарыстоўвае SSO з уласным серверам ключоў. Асноўны пароль для ўдзельнікаў гэтай арганізацыі больш не патрабуецца.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Выйсці з арганізацыі" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 6300ed3391f..18a1d8cf2f0 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Главната парола е премахната." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ използва еднократно удостоверяване със собствен сървър за ключове. Членовете на тази организация вече нямат нужда от главна парола за вписване.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "За членовете на следната организация вече не се изисква главна парола. Потвърдете домейна по-долу с администратора на организацията си." + }, + "organizationName": { + "message": "Име на организацията" + }, + "keyConnectorDomain": { + "message": "Домейн на конектора за ключове" }, "leaveOrganization": { "message": "Напускане на организацията" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Промяна на парола в риск" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Преместване" }, diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 3ca32f51395..b3a2b6e21ee 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 4e77b51467b..89015368c11 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 2f13de7c33b..5550e2d0c62 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "S'ha suprimit la contrasenya mestra." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ està utilitzant SSO amb un servidor autoallotjat de claus. Ja no es requereix una contrasenya mestra d'inici de sessió per als membres d'aquesta organització.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Abandona l'organització" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 49cfc8823b7..b6c3edd783a 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Hlavní heslo bylo odebráno" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ používá SSO s vlastním serverem s klíči. Hlavní heslo pro členy této organizace již není vyžadováno.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hlavní heslo již není vyžadováno pro členy následující organizace. Potvrďte níže uvedenou doménu u správce Vaší organizace." + }, + "organizationName": { + "message": "Název organizace" + }, + "keyConnectorDomain": { + "message": "Doména Key Connectoru" }, "leaveOrganization": { "message": "Opustit organizaci" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Změnit ohrožené heslo" }, + "cannotRemoveViewOnlyCollections": { + "message": "Nemůžete odebrat kolekce s oprávněními jen pro zobrazení: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Přesunout" }, diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 0031d2de121..28ed661423d 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index c92f0caf2d1..f547eea69a0 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Hovedadgangskode fjernet." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ bruger SSO med en selv-hostet nøgleserver. Organisationsmedlemmer behøver ikke længere hovedadgangskode for at logge ind.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Forlad organisation" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index da1272ef300..de39eba6f99 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master-Passwort entfernt" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ verwendet SSO mit einem selbst gehosteten Schlüsselserver. Ein Master-Passwort ist nicht mehr erforderlich, damit sich Mitglieder dieser Organisation anmelden können.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Für Mitglieder der folgenden Organisation ist kein Master-Passwort mehr erforderlich. Bitte bestätige die folgende Domain bei deinem Organisations-Administrator." + }, + "organizationName": { + "message": "Name der Organisation" + }, + "keyConnectorDomain": { + "message": "Key Connector-Domain" }, "leaveOrganization": { "message": "Organisation verlassen" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Gefährdetes Passwort ändern" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Verschieben" }, diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 1f4462ce4b5..3133488f805 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Ο κύριος κωδικός αφαιρέθηκε" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ χρησιμοποιεί SSO με έναν αυτοεξυπηρετητή κλειδιών. Ένας κύριος κωδικός πρόσβασης δεν απαιτείται πλέον για να συνδεθείτε για τα μέλη αυτού του οργανισμού.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Αποχώρηση από τον οργανισμό" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Μετακίνηση" }, diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 77e4222ac1f..cf4d0986e1f 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." + }, + "organizationName": { + "message": "Organisation name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organisation" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 109ace48efa..d82cb566a98 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." + }, + "organizationName": { + "message": "Organisation name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 51c350831a2..8e8cc09341f 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 3023fdd0359..154bb327689 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -232,7 +232,7 @@ "message": "Habilitar agente SSH" }, "enableSshAgentDesc": { - "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + "message": "Activa el agente SSH para firmar peticiones SSH directamente desde tu caja fuerte de Bitwarden." }, "enableSshAgentHelp": { "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." @@ -434,28 +434,28 @@ "message": "Website added" }, "addWebsite": { - "message": "Add website" + "message": "Añadir página web" }, "deleteWebsite": { - "message": "Delete website" + "message": "Eliminar página web" }, "owner": { - "message": "Owner" + "message": "Propietario" }, "addField": { "message": "Añadir campo" }, "editField": { - "message": "Edit field" + "message": "Editar campo" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "¿Estás seguro de que quieres eliminar permanentemente este adjunto?" }, "fieldType": { - "message": "Field type" + "message": "Tipo de campo" }, "fieldLabel": { - "message": "Field label" + "message": "Etiqueta del campo" }, "add": { "message": "Añadir" @@ -931,11 +931,11 @@ "message": "No volver a preguntar en este dispositivo durante 30 días" }, "selectAnotherMethod": { - "message": "Select another method", + "message": "Selecciona otro método", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "Usar tu código de recuperación" }, "insertU2f": { "message": "Inserta tu llave de seguridad en el puerto USB de tu equipo. Si tiene un botón, púlsalo." @@ -1028,7 +1028,7 @@ "message": "The authentication session timed out. Please restart the login process." }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "URL del servidor autoalojado", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -2001,10 +2001,10 @@ "message": "Free organizations cannot use attachments" }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1 campo necesita tu atención." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ campos necesitan tu atención.", "placeholders": { "count": { "content": "$1", @@ -2013,10 +2013,10 @@ } }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Tarjeta expirada" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "Si la has renovado, actualiza la información de la tarjeta" }, "verificationRequired": { "message": "Verificación requerida", @@ -2167,10 +2167,10 @@ "message": "La biometría del navegador requiere habilitar primero la biometría de escritorio en los ajustes." }, "biometricsManualSetupTitle": { - "message": "Automatic setup not available" + "message": "Configuración automática no disponible" }, "biometricsManualSetupDesc": { - "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + "message": "Debido al método de instalación, el soporte biométrico no pudo activarse automáticamente. ¿Quieres abrir la documentación sobre cómo hacerlo manualmente?" }, "personalOwnershipSubmitError": { "message": "Debido a una política de organización, tiene restringido el guardar elementos a su caja fuerte personal. Cambie la configuración de propietario a organización y elija entre las colecciones disponibles." @@ -2188,13 +2188,13 @@ "message": "Una política organizacional ha bloqueado la importación de elementos a su caja fuerte personal." }, "personalDetails": { - "message": "Personal details" + "message": "Datos personales" }, "identification": { - "message": "Identification" + "message": "Identificación" }, "contactInfo": { - "message": "Contact information" + "message": "Información de contacto" }, "allSends": { "message": "Todos los Send", @@ -2363,7 +2363,7 @@ "message": "Leer clave de seguridad" }, "awaitingSecurityKeyInteraction": { - "message": "Awaiting security key interaction..." + "message": "Esperando interacción de la clave de seguridad..." }, "hideEmail": { "message": "Ocultar mi dirección de correo electrónico a los destinatarios." @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Contraseña maestra eliminada." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ está usando SSO con un servidor de claves autoalojado. Los miembros de esta organización ya no necesitarán una contraseña maestra para iniciar sesión.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Nombre de la organización" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Abandonar organización" @@ -2946,7 +2946,7 @@ "message": "Are you trying to access your account?" }, "accessAttemptBy": { - "message": "Access attempt by $EMAIL$", + "message": "Intento de acceso de $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -3016,7 +3016,7 @@ "message": "Inicio de sesión solicitado" }, "accountAccessRequested": { - "message": "Account access requested" + "message": "Acceso a la cuenta solicitado" }, "creatingAccountOn": { "message": "Creando una cuenta en" @@ -3161,10 +3161,10 @@ "message": "Trust organization" }, "trust": { - "message": "Trust" + "message": "Confiar" }, "doNotTrust": { - "message": "Do not trust" + "message": "No confiar" }, "organizationNotTrusted": { "message": "Organization is not trusted" @@ -3623,7 +3623,7 @@ "message": "Login credentials" }, "additionalOptions": { - "message": "Additional options" + "message": "Opciones adicionales" }, "itemHistory": { "message": "Item history" @@ -3659,10 +3659,10 @@ "message": "authenticate to a server" }, "sshActionSign": { - "message": "sign a message" + "message": "firmar un mensaje" }, "sshActionGitSign": { - "message": "sign a git commit" + "message": "firmar una confirmación git" }, "unknownApplication": { "message": "Una aplicación" @@ -3683,7 +3683,7 @@ "message": "File saved to device. Manage from your device downloads." }, "allowScreenshots": { - "message": "Allow screen capture" + "message": "Permitir captura de pantalla" }, "allowScreenshotsDesc": { "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." @@ -3703,11 +3703,20 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Mover" }, "newFolder": { - "message": "New folder" + "message": "Nueva carpeta" }, "folderName": { "message": "Folder Name" @@ -3724,7 +3733,7 @@ "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "Página web", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 922ab7e36a6..1cc526ecfba 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Ülemparool on eemaldatud." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ kasutab SSO-d koos enda majutatud võtmeserveriga. Selle organisatsiooni liikmed ei pea sisselogimisel enam ülemparooli kasutama.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lahku organisatsioonist" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 3bc76d8427c..ff8d91873b7 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Pasahitz nagusia ezabatua." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ ostatatze propioko gako-zerbitzari batekin SSO erabiltzen ari da. Dagoeneko ez da pasahitz nagusirik behar erakunde honetako kideentzat saioa hasteko.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Utzi erakundea" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index 4bcbbd3ac33..e0050ea092a 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -27,7 +27,7 @@ "message": "یادداشت امن" }, "typeSshKey": { - "message": "SSH key" + "message": "کلید SSH" }, "folders": { "message": "پوشه‌ها" @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "خوش آمدید" }, "moveToOrgDesc": { "message": "سازمانی را انتخاب کنید که می‌خواهید این مورد را به آن منتقل کنید. انتقال به یک سازمان، مالکیت مورد را به آن سازمان منتقل می‌کند. پس از انتقال این مورد، دیگر مالک مستقیم آن نخواهید بود." @@ -181,16 +181,16 @@ "message": "نشانی" }, "sshPrivateKey": { - "message": "Private key" + "message": "کلید خصوصی" }, "sshPublicKey": { - "message": "Public key" + "message": "کلید عمومی" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "اثر انگشت" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "نوع کلید" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -205,55 +205,55 @@ "message": "RSA 4096-Bit" }, "sshKeyGenerated": { - "message": "A new SSH key was generated" + "message": "یک کلید SSH جدید ایجاد شد" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "کلمه عبور وارد شده اشتباه است." }, "importSshKey": { - "message": "Import" + "message": "درون ریزی" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "تأیید کلمه عبور" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "کلمه عبور کلید SSH را وارد کنید." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "کلمه عبور را وارد کنید" }, "sshAgentUnlockRequired": { - "message": "Please unlock your vault to approve the SSH key request." + "message": "لطفاً برای تأیید درخواست کلید SSH، گاوصندوق خود را باز کنید." }, "sshAgentUnlockTimeout": { - "message": "SSH key request timed out." + "message": "درخواست کلید SSH منقضی شد." }, "enableSshAgent": { - "message": "Enable SSH agent" + "message": "عامل SSH را فعال کنید" }, "enableSshAgentDesc": { - "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + "message": "عامل SSH را فعال کنید تا درخواست‌های SSH را مستقیماً از گاوصندوق Bitwarden خود امضا کنید." }, "enableSshAgentHelp": { - "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + "message": "عامل SSH یک سرویس مخصوص توسعه‌دهندگان است که به شما امکان می‌دهد درخواست‌های SSH را مستقیماً از گاوصندوق Bitwarden خود امضا کنید." }, "sshAgentPromptBehavior": { - "message": "Ask for authorization when using SSH agent" + "message": "در هنگام استفاده از عامل SSH درخواست تأیید مجوز شود" }, "sshAgentPromptBehaviorDesc": { - "message": "Choose how to handle SSH-agent authorization requests." + "message": "نحوه‌ی رسیدگی به درخواست‌های مجوز عامل SSH را انتخاب کنید." }, "sshAgentPromptBehaviorHelp": { - "message": "Remember SSH authorizations" + "message": "مجوزهای SSH را به خاطر بسپار" }, "sshAgentPromptBehaviorAlways": { - "message": "Always" + "message": "همیشه" }, "sshAgentPromptBehaviorNever": { - "message": "Never" + "message": "هرگز" }, "sshAgentPromptBehaviorRememberUntilLock": { - "message": "Remember until vault is locked" + "message": "تا زمانی که گاوصندوق قفل شود، به خاطر بسپار" }, "premiumRequired": { "message": "در نسخه پرمیوم کار می‌کند" @@ -268,17 +268,17 @@ "message": "خطا" }, "decryptionError": { - "message": "Decryption error" + "message": "خطای رمزگشایی" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden could not decrypt the vault item(s) listed below." + "message": "Bitwarden نتوانست مورد(های) گاوصندوق فهرست شده زیر را رمزگشایی کند." }, "contactCSToAvoidDataLossPart1": { - "message": "Contact customer success", + "message": "با بخش پشتیبانی مشتریان تماس بگیرید", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { - "message": "to avoid additional data loss.", + "message": "برای جلوگیری از، از دست دادن داده‌های بیشتر.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "january": { @@ -355,7 +355,7 @@ "message": "تولید کلمه عبور" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "تولید عبارت عبور" }, "type": { "message": "نوع" @@ -412,16 +412,16 @@ "message": "کلید احراز هویت (TOTP)" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "کلید احراز هویت" }, "autofillOptions": { - "message": "Autofill options" + "message": "گزینه‌های پر کردن خودکار" }, "websiteUri": { - "message": "Website (URI)" + "message": "وب‌سایت (نشانی اینترنتی)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "وب‌سایت (نشانی اینترنتی) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -431,49 +431,49 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "وب‌سایت اضافه شد" }, "addWebsite": { - "message": "Add website" + "message": "افزودن وب‌سایت" }, "deleteWebsite": { - "message": "Delete website" + "message": "حذف وبسایت" }, "owner": { - "message": "Owner" + "message": "مالک" }, "addField": { - "message": "Add field" + "message": "افزودن فیلد" }, "editField": { - "message": "Edit field" + "message": "ویرایش فیلد" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "آیا مطمئن هستید که می‌خواهید این پرونده پیوست را به‌طور دائمی حذف کنید؟" }, "fieldType": { - "message": "Field type" + "message": "نوع فیلد" }, "fieldLabel": { - "message": "Field label" + "message": "برچسب فیلد" }, "add": { - "message": "Add" + "message": "افزودن" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "برای داده‌هایی مانند سوالات امنیتی از فیلدهای متنی استفاده کنید" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "برای داده‌های حساس مانند کلمه عبور از فیلدهای مخفی استفاده کنید" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "اگر می‌خواهید فیلدهای تیک‌دار فرم را به‌صورت خودکار پر کنید، مانند گزینه به یاد سپردن ایمیل، از کادرهای انتخاب استفاده کنید" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "وقتی در پر کردن خودکار برای یک وب‌سایت خاص به مشکل برخوردید، از فیلد مرتبط استفاده کنید." }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "شناسه Html، نام، aria-label یا محل نگهدار فیلد را وارد کنید." }, "folder": { "message": "پوشه" @@ -501,7 +501,7 @@ "description": "This describes a field that is 'linked' (related) to another field." }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "کادر انتخاب" }, "linkedValue": { "message": "مقدار پیوند شده", @@ -535,7 +535,7 @@ "message": "مورد به زباله‌ها فرستاده شد" }, "overwritePasswordConfirmation": { - "message": "آیا از بازنویسی بر روی پسورد فعلی مطمئن هستید؟" + "message": "آیا از بازنویسی بر روی کلمه عبور فعلی مطمئن هستید؟" }, "overwriteUsername": { "message": "بازنویسی نام کاربری" @@ -560,13 +560,13 @@ "message": "کپی کلمه عبور" }, "regenerateSshKey": { - "message": "Regenerate SSH key" + "message": "تولید مجدد کلید SSH" }, "copySshPrivateKey": { - "message": "Copy SSH private key" + "message": "کپی کلید خصوصی SSH" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "کپی عبارت عبور", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -579,7 +579,7 @@ "message": "طول" }, "passwordMinLength": { - "message": "حداقل طول رمز عبور" + "message": "حداقل طول کلمه عبور" }, "uppercase": { "message": "حروف بزرگ (A-Z)", @@ -597,11 +597,11 @@ "message": "نویسه‌های ویژه (!@#$%^&*)" }, "include": { - "message": "Include", + "message": "شامل", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "شامل حروف بزرگ باشد", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -609,7 +609,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "شامل حروف کوچک باشد", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -617,7 +617,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "شامل اعداد", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -625,7 +625,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "افزودن کاراکترهای خاص", "description": "Full description for the password generator special characters checkbox" }, "numWords": { @@ -656,11 +656,11 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "از کاراکترهای مبهم خودداری کن", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "نیازمندی‌های سیاست سازمانی بر گزینه‌های تولید کننده شما اعمال شده‌اند.", "description": "Indicates that a policy limits the credential generator screen." }, "searchCollection": { @@ -719,37 +719,37 @@ "message": "ایجاد حساب کاربری" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "در Bitwarden تازه وارد هستید؟" }, "setAStrongPassword": { - "message": "تنظیم رمز عبور قوی" + "message": "تنظیم کلمه عبور قوی" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "ایجاد حساب‌تان را با تنظیم کردن رمز عبور تکمیل کنید" + "message": "ایجاد حساب کاربری خود را با تنظیم کلمه عبور تکمیل کنید" }, "logIn": { "message": "ورود" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "وارد Bitwarden شوید" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "کدی را که به ایمیل شما ارسال شده وارد کنید" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "کد سامانه تأیید کننده را وارد نمایید" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "برای احراز هویت، کلید YubiKey خود را فشار دهید" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "با کلید عبور وارد شوید" }, "loginWithDevice": { - "message": "Log in with device" + "message": "ورود با دستگاه" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "استفاده از ورود تک مرحله‌ای" }, "submit": { "message": "ثبت" @@ -770,7 +770,7 @@ "message": "یادآور کلمه عبور اصلی (اختیاری)" }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "اگر کلمه عبور خود را فراموش کنید، یادآور کلمه عبور می‌تواند به ایمیل شما ارسال شود. حداکثر $CURRENT$/$MAXIMUM$ کاراکتر.", "placeholders": { "current": { "content": "$1", @@ -783,19 +783,19 @@ } }, "masterPassword": { - "message": "Master password" + "message": "کلمه عبور اصلی" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "کلمه‌های عبور اصلی در صورت فراموشی قابل بازیابی نیستند!" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "تأیید کلمه عبور اصلی" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "یادآور کلمه عبور اصلی" }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "امتیاز قدرت کلمه عبور $SCORE$", "placeholders": { "score": { "content": "$1", @@ -804,10 +804,10 @@ } }, "joinOrganization": { - "message": "Join organization" + "message": "به سازمان بپیوندید" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "به $ORGANIZATIONNAME$ بپیوندید", "placeholders": { "organizationName": { "content": "$1", @@ -816,22 +816,22 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "با تعیین یک کلمه عبور اصلی، عضویت خود در این سازمان را کامل کنید." }, "settings": { "message": "تنظیمات" }, "accountEmail": { - "message": "Account email" + "message": "حساب ایمیل" }, "requestHint": { - "message": "Request hint" + "message": "درخواست راهنمایی" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "درخواست یادآور کلمه عبور" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" + "message": "نشانی ایمیل حساب کاربری خود را وارد کنید تا راهنمای کلمه عبور برای شما ارسال شود" }, "getMasterPasswordHint": { "message": "دریافت یادآور کلمه عبور اصلی" @@ -871,10 +871,10 @@ "message": "حساب کاربری جدید شما ساخته شد! حالا می‌توانید وارد شوید." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "حساب کاربری جدید شما ایجاد شده است!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "شما با موفقیت وارد شدید!" }, "masterPassSent": { "message": "ما یک ایمیل همراه با یادآور کلمه عبور اصلی برایتان ارسال کردیم." @@ -907,10 +907,10 @@ "message": "کد تأیید مورد نیاز است." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "احراز هویت لغو شد یا بیش از حد طول کشید. لطفاً دوباره تلاش کنید." }, "openInNewTab": { - "message": "Open in new tab" + "message": "گشودن در زبانهٔ جدید" }, "invalidVerificationCode": { "message": "کد تأیید نامعتبر است" @@ -928,14 +928,14 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "در این دستگاه به مدت ۳۰ روز دوباره نپرس" }, "selectAnotherMethod": { - "message": "Select another method", + "message": "انتخاب روش دیگر", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "از کد بازیابی‌تان استفاده کنید" }, "insertU2f": { "message": "کلید امنیتی خود را وارد پورت USB رایانه کنید، اگر دکمه‌ای دارد آن را بفشارید." @@ -950,17 +950,17 @@ "message": "برنامه احراز هویت" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "کدی را وارد کنید که توسط یک برنامه احراز هویت مانند Bitwarden Authenticator تولید شده است.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "کلید امنیت Yubico OTP" }, "yubiKeyDesc": { "message": "از یک YubiKey برای دسترسی به حسابتان استفاده کنید. همراه با دستگاه‌های YubiKey 4 ،4 Nano ،NEO کار می‌کند." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "کدی را وارد کنید که توسط Duo Security تولید شده است.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -968,13 +968,13 @@ "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "verifyYourIdentity": { - "message": "Verify your Identity" + "message": "هویت خود را تأیید کنید" }, "weDontRecognizeThisDevice": { - "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + "message": "ما این دستگاه را نمی‌شناسیم. برای تأیید هویت خود، کدی را که به ایمیلتان ارسال شده وارد کنید." }, "continueLoggingIn": { - "message": "Continue logging in" + "message": "ادامه ورود" }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" @@ -986,7 +986,7 @@ "message": "ایمیل" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "کدی را که به ایمیل شما ارسال شده وارد کنید." }, "loginUnavailable": { "message": "ورود به سیستم در دسترس نیست" @@ -1001,19 +1001,19 @@ "message": "گزینه‌های ورود دو مرحله‌ای" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "انتخاب ورود دو مرحله‌ای" }, "selfHostedEnvironment": { "message": "محیط خود میزبان" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "نشانی اینترنتی پایه نصب Bitwarden خود را که به‌صورت داخلی میزبانی شده مشخص کنید.\nمثال: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "برای پیکربندی پیشرفته، می‌توانید نشانی اینترنتی پایه هر سرویس را به‌صورت مستقل مشخص کنید." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "شما باید یا نشانی اینترنتی پایه سرور را اضافه کنید، یا حداقل یک محیط سفارشی تعریف کنید." }, "customEnvironment": { "message": "محیط سفارشی" @@ -1022,13 +1022,13 @@ "message": "نشانی اینترنتی سرور" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "پایان زمان احراز هویت" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "نشست احراز هویت منقضی شد. لطفاً فرایند ورود را دوباره شروع کنید." }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "نشانی اینترنتی سرور خود میزبان", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1059,7 +1059,7 @@ "message": "خیر" }, "location": { - "message": "Location" + "message": "موقعیت" }, "overwritePassword": { "message": "بازنویسی کلمه عبور" @@ -1080,16 +1080,16 @@ "message": "نشست ورود شما منقضی شده است." }, "restartRegistration": { - "message": "Restart registration" + "message": "ثبت‌نام را دوباره آغاز کنید" }, "expiredLink": { - "message": "Expired link" + "message": "پیوند منقضی شد" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "لطفاً ثبت نام را مجدداً شروع کنید یا دوباره وارد شوید." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "ممکن است قبلاً حساب کاربری داشته باشید" }, "logOutConfirmation": { "message": "آیا مطمئنید که می‌خواهید خارج شوید؟" @@ -1175,16 +1175,16 @@ "message": "گاوصندوق شما قفل شده است. برای ادامه هویت خود را تأیید کنید." }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "حساب کاربری شما قفل شده است" }, "or": { - "message": "or" + "message": "یا" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "با استفاده از بیومتریک باز کنید" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "باز کردن قفل با کلمه عبور اصلی" }, "unlock": { "message": "باز کردن قفل" @@ -1215,7 +1215,7 @@ "message": "متوقف شدن گاو‌صندوق" }, "vaultTimeout1": { - "message": "Timeout" + "message": "پایان زمان" }, "vaultTimeoutDesc": { "message": "انتخاب کنید که گاو‌صندوق شما چه زمانی عمل توقف زمانی گاوصندوق را انجام دهد." @@ -1422,7 +1422,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "کپی ایمیل" }, "copySecurityCode": { "message": "کپی کد امنیتی", @@ -1453,7 +1453,7 @@ "message": "گزینه های ورود اضافی دو مرحله ای مانند YubiKey و Duo." }, "premiumSignUpReports": { - "message": "گزارش‌های بهداشت رمز عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما." + "message": "گزارش‌های بهداشت کلمه عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما." }, "premiumSignUpTotp": { "message": "تولید کننده کد تأیید (2FA) از نوع TOTP برای ورودهای موجود در گاوصندوقتان." @@ -1468,7 +1468,7 @@ "message": "خرید پرمیوم" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "می‌توانید نسخه پرمیوم را از تنظیمات حساب کاربری خود در اپلیکیشن وب Bitwarden خریداری کنید." }, "premiumCurrentMember": { "message": "شما یک عضو پرمیوم هستید!" @@ -1492,13 +1492,13 @@ "message": "تاریخچه کلمه عبور" }, "generatorHistory": { - "message": "Generator history" + "message": "تاریخچه تولید کننده" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "پاک کردن تاریخچه تولید کننده" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "اگر ادامه دهید، تمام ورودی‌ها به‌طور دائمی از تاریخچه تولید کننده حذف خواهند شد. آیا مطمئن هستید که می‌خواهید ادامه دهید؟" }, "clear": { "message": "پاک کردن", @@ -1508,13 +1508,13 @@ "message": "هیچ کلمه عبوری برای فهرست کردن وجود ندارد." }, "clearHistory": { - "message": "Clear history" + "message": "پاک کردن تاریخچه" }, "nothingToShow": { - "message": "Nothing to show" + "message": "چیزی برای نشان دادن موجود نیست" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "شما اخیراً چیزی تولید نکرده‌اید" }, "undo": { "message": "بازگرداندن" @@ -1591,13 +1591,13 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "کپی موفق بود" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "خطای به‌روزرسانی توکن دسترسی" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "هیچ توکن به‌روزرسانی یا کلید API یافت نشد. لطفاً از حساب کاربری خود خارج شده و دوباره وارد شوید." }, "help": { "message": "راهنما" @@ -1687,7 +1687,7 @@ "description": "ex. Date this password was updated" }, "exportFrom": { - "message": "صادرات از" + "message": "برون ریزی از" }, "exportVault": { "message": "برون ریزی گاوصندوق" @@ -1696,31 +1696,31 @@ "message": "فرمت پرونده" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "این پرونده برون ریزی با کلمه عبور محافظت می‌شود و برای رمزگشایی به کلمه عبور پرونده نیاز دارد." }, "filePassword": { - "message": "رمز فایل" + "message": "کلمه عبور پرونده" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "این کلمه عبور برای برون ریزی و درون ریزی این پرونده استفاده می‌شود" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "برای رمزگذاری برون ریزی و محدود کردن درون ریزی فقط به حساب کاربری فعلی Bitwarden، از کلید رمزگذاری حساب خود که از نام کاربری و کلمه عبور اصلی حساب شما مشتق شده است استفاده کنید." }, "passwordProtected": { "message": "محافظت ‌شده با کلمه عبور" }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "یک کلمه عبور برای پرونده به‌منظور رمزگذاری تنظیم کنید تا برون ریزی و درون ریزی آن به هر حساب Bitwarden با استفاده از کلمه عبور رمزگشایی شود." }, "exportTypeHeading": { - "message": "نوع صادرات" + "message": "نوع برون ریزی" }, "accountRestricted": { "message": "حساب کاربری محدود شده است" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "عدم تطابق \"رمز فایل\" و \"تایید رمز فایل\" با یکدیگر." + "message": "عدم تطابق \"کلمه عبور پرونده\" و \"تأیید کلمه عبور پرونده\" با یکدیگر." }, "done": { "message": "انجام شد" @@ -1739,7 +1739,7 @@ "message": "این برون ریزی با استفاده از کلید رمزگذاری حساب شما، اطلاعاتتان را رمزگذاری می کند. اگر زمانی کلید رمزگذاری حساب خود را بچرخانید، باید دوباره خروجی بگیرید، چون قادر به رمزگشایی این پرونده برون ریزی نخواهید بود." }, "encExportAccountWarningDesc": { - "message": "کلیدهای رمزگذاری حساب برای هر حساب کاربری Bitwarden منحصر به فرد است، بنابراین نمی‌توانید برون ریزی رمزگذاری شده را به حساب دیگری وارد کنید." + "message": "کلیدهای رمزگذاری حساب برای هر حساب کاربری Bitwarden منحصر به فرد است، بنابراین نمی‌توانید برون ریزی رمزگذاری شده را به حساب دیگری درون ریزی کنید." }, "noOrganizationsList": { "message": "شما به هیچ سازمانی تعلق ندارید. سازمان‌ها به شما اجازه می‌دهند تا داده‌های خود را با کاربران دیگر به صورت امن به اشتراک بگذارید." @@ -1776,7 +1776,7 @@ "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "unlockWithPin": { - "message": "باز کردن با پین" + "message": "باز کردن با کد پین" }, "setYourPinCode": { "message": "کد پین خود را برای باز کردن Bitwarden تنظیم کنید. اگر به طور کامل از برنامه خارج شوید، تنظیمات پین شما از بین می‌رود." @@ -1788,7 +1788,7 @@ "message": "کد پین غیر معتبر است." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "تعداد تلاش‌های ناموفق کد پین زیاد شد. خارج می‌شوید." }, "unlockWithWindowsHello": { "message": "باز کردن با Windows Hello" @@ -1797,7 +1797,7 @@ "message": "تنظیمات اضافی Windows Hello" }, "unlockWithPolkit": { - "message": "Unlock with system authentication" + "message": "باز کردن قفل با احراز هویت سیستم" }, "windowsHelloConsentMessage": { "message": "تأیید برای Bitwarden." @@ -1815,22 +1815,22 @@ "message": "درخواست Windows Hello در هنگام راه اندازی" }, "autoPromptPolkit": { - "message": "Ask for system authentication on launch" + "message": "در زمان اجرا درخواست احراز هویت سیستم را بده" }, "autoPromptTouchId": { "message": "درخواست Touch ID در هنگام راه اندازی" }, "requirePasswordOnStart": { - "message": "هنگام شروع برنامه، رمز عبور یا پین مورد نیاز است" + "message": "هنگام شروع برنامه، کلمه عبور یا کد پین مورد نیاز است" }, "requirePasswordWithoutPinOnStart": { - "message": "Require password on app start" + "message": "هنگام شروع برنامه، کلمه عبور مورد نیاز است" }, "recommendedForSecurity": { "message": "برای امنیت توصیه می‌شود." }, "lockWithMasterPassOnRestart1": { - "message": "Lock with master password on restart" + "message": "در زمان شروع مجدد، با کلمه عبور اصلی قفل کن" }, "deleteAccount": { "message": "حذف حساب" @@ -1842,10 +1842,10 @@ "message": "حذف حساب شما دائمی است. نمی‌توان آن را برگرداند." }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "قادر به حذف حساب کاربری نیستیم" }, "cannotDeleteAccountDesc": { - "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." + "message": "این اقدام قابل انجام نیست زیرا حساب کاربری شما متعلق به یک سازمان است. برای جزئیات بیشتر با مدیر سازمان خود تماس بگیرید." }, "accountDeleted": { "message": "حساب حذف شد" @@ -1916,7 +1916,7 @@ "description": "Verb form: to make secure or inaccessible by" }, "trash": { - "message": "زباله‌ها", + "message": "سطل زباله", "description": "Noun: a special folder to hold deleted items" }, "searchTrash": { @@ -1950,15 +1950,15 @@ "message": "تنظیم کلمه عبور اصلی" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "Your organization permissions were updated, requiring you to set a master password.", + "message": "مجوزهای سازمان شما به‌روزرسانی شد، باید یک کلمه عبور اصلی تنظیم کنید.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Your organization requires you to set a master password.", + "message": "سازمانتان از شما می‌خواهد که یک کلمه عبور اصلی تنظیم کنید.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "از میان $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -1967,10 +1967,10 @@ } }, "cardDetails": { - "message": "Card details" + "message": "جزئیات کارت" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "$BRAND$ جزئیات", "placeholders": { "brand": { "content": "$1", @@ -1979,32 +1979,32 @@ } }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "درباره احراز هویت کننده‌ها بیشتر بدانید" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "کپی کلید احراز هویت (TOTP)" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "تأیید دو مرحله‌ای را بدون دردسر کنید" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden می‌تواند کدهای تأیید دو مرحله‌ای را ذخیره و پر کند. کلید را کپی کرده و در این فیلد قرار دهید." }, "totpHelperWithCapture": { - "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + "message": "Bitwarden می‌تواند کدهای تأیید دو مرحله‌ای را ذخیره و پر کند. برای اسکن کد QR احراز هویت کننده این وب‌سایت، روی آیکون دوربین کلیک کنید یا کلید را کپی کرده و در این فیلد قرار دهید." }, "premium": { - "message": "Premium", + "message": "پرمیوم", "description": "Premium membership" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "سازمان‌های رایگان نمی‌توانند از پرونده‌های پیوست استفاده کنند" }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "۱ فیلد به توجه شما نیاز دارد." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "فیلدهای $COUNT$ به توجه شما نیاز دارند.", "placeholders": { "count": { "content": "$1", @@ -2013,13 +2013,13 @@ } }, "cardExpiredTitle": { - "message": "Expired card" + "message": "تاریخ کارت منقضی شده است" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "اگر تمدید کرده‌اید، اطلاعات کارت را به‌روزرسانی کنید" }, "verificationRequired": { - "message": "تایید مورد نیاز است", + "message": "تأیید لازم است", "description": "Default title for the user verification dialog." }, "currentMasterPass": { @@ -2074,10 +2074,10 @@ "message": "کلمه عبور اصلی جدید شما از شرایط سیاست پیروی نمی‌کند." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "پیشنهادها، اعلان‌ها و فرصت‌های تحقیقاتی Bitwarden را در صندوق ورودی خود دریافت کنید." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "لغو اشتراک" }, "atAnyTime": { "message": "در هر زمان." @@ -2098,7 +2098,7 @@ "message": "فعال کردن ادغام مرورگر" }, "enableBrowserIntegrationDesc1": { - "message": "Used to allow biometric unlock in browsers that are not Safari." + "message": "برای فعال‌سازی باز کردن قفل با بیومتریک در مرورگرهایی به‌جز Safari استفاده می‌شود." }, "enableDuckDuckGoBrowserIntegration": { "message": "اجازه ادغام مرورگر DuckDuckGo را بدهید" @@ -2110,10 +2110,10 @@ "message": "ادغام مرورگر پشتیبانی نمی‌شود" }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "خطای فعال کردن ادغام مرورگر" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "خطایی هنگام فعال‌سازی یکپارچه سازی مرورگر رخ داده است." }, "browserIntegrationMasOnlyDesc": { "message": "متأسفانه در حال حاضر ادغام مرورگر فقط در نسخه Mac App Store پشتیبانی می‌شود." @@ -2134,7 +2134,7 @@ "message": "استفاده از شتاب سخت افزاری" }, "enableHardwareAccelerationDesc": { - "message": "By default this setting is ON. Turn OFF only if you experience graphical issues. Restart is required." + "message": "به طور پیش‌فرض این تنظیم روشن است. فقط در صورت مواجهه با مشکلات گرافیکی آن را خاموش کنید. نیاز به راه‌اندازی مجدد دارد." }, "approve": { "message": "تأیید" @@ -2167,16 +2167,16 @@ "message": "بیومتریک مرورگر ابتدا نیاز به فعالسازی بیومتریک دسکتاپ در تنظیمات دارد." }, "biometricsManualSetupTitle": { - "message": "Automatic setup not available" + "message": "راه‌اندازی خودکار در دسترس نیست" }, "biometricsManualSetupDesc": { - "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + "message": "به دلیل روش نصب، پشتیبانی از بیومتریک به‌صورت خودکار فعال نشد. آیا می‌خواهید مستندات نحوه انجام این کار به‌صورت دستی را باز کنید؟" }, "personalOwnershipSubmitError": { "message": "به دلیل سیاست پرمیوم، برای ذخیره موارد در گاوصندوق شخصی خود محدود شده اید. گزینه مالکیت را به یک سازمان تغییر دهید و مجموعه های موجود را انتخاب کنید." }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "کلمه عبور جدید شما نمی‌تواند با کلمه عبور فعلی‌تان یکسان باشد." }, "hintEqualsPassword": { "message": "اشاره به کلمه عبور شما نمی‌تواند همان کلمه عبور شما باشد." @@ -2185,16 +2185,16 @@ "message": "سیاست سازمانی بر تنظیمات مالکیت شما تأثیر می‌گذارد." }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "یک سیاست سازمانی، درون ریزی موارد به گاوصندوق فردی شما را مسدود کرده است." }, "personalDetails": { - "message": "Personal details" + "message": "جزئیات شخصی" }, "identification": { - "message": "Identification" + "message": "شناسایی" }, "contactInfo": { - "message": "Contact information" + "message": "اطلاعات تماس" }, "allSends": { "message": "همه ارسال ها", @@ -2360,10 +2360,10 @@ "message": "تأیید اعتبار در WebAuthn" }, "readSecurityKey": { - "message": "Read security key" + "message": "خواندن کلید امنیتی" }, "awaitingSecurityKeyInteraction": { - "message": "Awaiting security key interaction..." + "message": "در انتظار تعامل با کلید امنیتی..." }, "hideEmail": { "message": "نشانی ایمیلم را از گیرندگان مخفی کن." @@ -2375,7 +2375,7 @@ "message": "تأیید ایمیل لازم است" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "ایمیل تأیید شد" }, "emailVerificationRequiredDesc": { "message": "برای استفاده از این ویژگی باید ایمیل خود را تأیید کنید." @@ -2402,22 +2402,22 @@ "message": "کلمه عبور اصلی شما با یک یا چند سیاست سازمان‌تان مطابقت ندارد. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را به‌روز کنید. در صورت ادامه، شما از نشست فعلی خود خارج می‌شوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "سازمان شما رمزگذاری دستگاه‌های مورد اعتماد را غیرفعال کرده است. لطفاً برای دسترسی به گاوصندوق خود یک کلمه عبور اصلی تنظیم کنید." }, "tryAgain": { "message": "دوباره سعی کنید" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "برای این اقدام تأیید لازم است. یک کد پین تعیین کنید تا ادامه دهید." }, "setPin": { - "message": "تنظیم PIN" + "message": "تنظیم کد پین" }, "verifyWithBiometrics": { - "message": "تایید با استفاده از بیومتریک" + "message": "تأیید با استفاده از بیومتریک" }, "awaitingConfirmation": { - "message": "در انتظار تایید" + "message": "در انتظار تأیید" }, "couldNotCompleteBiometrics": { "message": "تکمیل بیومتریک ممکن نشد." @@ -2426,10 +2426,10 @@ "message": "نیازمند روش دیگری هستید؟" }, "useMasterPassword": { - "message": "استفاده از رمز عبور اصلی" + "message": "استفاده از کلمه عبور اصلی" }, "usePin": { - "message": "استفاده از پین" + "message": "استفاده از کد پین" }, "useBiometrics": { "message": "استفاده از بیومتریک" @@ -2447,7 +2447,7 @@ "message": "دقیقه" }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "حداکثر $HOURS$ ساعت و $MINUTES$ دقیقه.", "placeholders": { "hours": { "content": "$1", @@ -2489,7 +2489,7 @@ "message": "مهلت زمانی شما بیش از محدودیت های تعیین شده توسط سازمانتان است." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "دعوتنامه پذیرفته شد" }, "resetPasswordPolicyAutoEnroll": { "message": "ثبت نام خودکار" @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "کلمه عبور اصلی حذف شد" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ در حال استفاده از SSO با یک سرور کلید خود میزبان است. برای ورود اعضای این سازمان دیگر نیازی به کلمه عبور اصلی نیست.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "برای اعضای سازمان زیر، کلمه عبور اصلی دیگر لازم نیست. لطفاً دامنه زیر را با مدیر سازمان خود تأیید کنید." + }, + "organizationName": { + "message": "نام سازمان" + }, + "keyConnectorDomain": { + "message": "دامنه رابط کلید" }, "leaveOrganization": { "message": "ترک سازمان" @@ -2582,7 +2582,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included", + "message": "فقط موردهای فردی گاوصندوق شامل پیوست‌ها که به $EMAIL$ مرتبط هستند برون ریزی خواهند شد. موردهای گاوصندوق سازمانی شامل نمی‌شوند", "placeholders": { "email": { "content": "$1", @@ -2591,10 +2591,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "در حال برون ریزی گاوصندوق سازمان" }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "فقط گاوصدوق سازمان مرتبط با $ORGANIZATION$ برون ریزی خواهد شد. موارد موجود در گاوصندوق‌های فردی یا سایر سازمان‌ها شامل نمی‌شوند.", "placeholders": { "organization": { "content": "$1", @@ -2606,7 +2606,7 @@ "message": "قفل شده" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "گاوصندوق‌تان قفل شد" }, "unlocked": { "message": "باز شده" @@ -2628,13 +2628,13 @@ "message": "ایجاد نام کاربری" }, "generateEmail": { - "message": "Generate email" + "message": "تولید ایمیل" }, "usernameGenerator": { - "message": "Username generator" + "message": "تولید کننده نام کاربری" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "مقدار باید بین $MIN$ و $MAX$ باشد.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -2648,7 +2648,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " برای تولید یک کلمه عبور قوی، از $RECOMMENDED$ کاراکتر یا بیشتر استفاده کنید.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2658,7 +2658,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " برای تولید یک عبارت عبور قوی، از $RECOMMENDED$ واژه یا بیشتر استفاده کنید.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -2684,7 +2684,7 @@ "message": "از صندوق ورودی پیکربندی شده دامنه خود استفاده کنید." }, "useThisEmail": { - "message": "Use this email" + "message": "از این ایمیل استفاده شود" }, "random": { "message": "تصادفی" @@ -2714,15 +2714,15 @@ "message": "یک نام مستعار ایمیل با یک سرویس ارسال خارجی ایجاد کنید." }, "forwarderDomainName": { - "message": "Email domain", + "message": "دامنه ایمیل", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "دامنه‌ای را انتخاب کنید که توسط سرویس انتخاب شده پشتیبانی می‌شود", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ خطا: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2736,11 +2736,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "تولید شده توسط Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "وب‌سایت: $WEBSITE$. تولید شده توسط Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2750,7 +2750,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "توکن API نامعتبر برای $SERVICENAME$", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2760,7 +2760,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "توکن API نامعتبر برای $SERVICENAME$: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2774,7 +2774,7 @@ } }, "forwaderInvalidOperation": { - "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "message": "$SERVICENAME$ درخواست شما را رد کرد. لطفاً برای دریافت کمک با ارائه‌دهنده سرویس خود تماس بگیرید.", "description": "Displayed when the user is forbidden from using the API by the forwarding service.", "placeholders": { "servicename": { @@ -2784,7 +2784,7 @@ } }, "forwaderInvalidOperationWithMessage": { - "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "message": "$SERVICENAME$ درخواست شما را رد کرد: $ERRORMESSAGE$", "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2798,7 +2798,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "دریافت شناسه حساب ایمیل ماسک شده از $SERVICENAME$ امکان‌پذیر نیست.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2808,7 +2808,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "دامنه $SERVICENAME$ نامعتبر.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2818,7 +2818,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "آدرس $SERVICENAME$ نامعتبر.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2828,7 +2828,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "خطای $SERVICENAME$ نامعلومی رخ داد.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2838,7 +2838,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "فرواردکننده ناشناخته: $SERVICENAME$.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -2897,25 +2897,25 @@ "message": "ورود به سیستم آغاز شد" }, "logInRequestSent": { - "message": "Request sent" + "message": "درخواست ارسال شد" }, "notificationSentDevice": { "message": "یک اعلان به دستگاه شما ارسال شده است." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "یک اعلان به دستگاه شما ارسال شده است" }, "notificationSentDevicePart1": { - "message": "Unlock Bitwarden on your device or on the " + "message": "قفل Bitwarden را روی دستگاه خود باز کنید یا روی " }, "notificationSentDeviceAnchor": { - "message": "web app" + "message": "برنامه وب" }, "notificationSentDevicePart2": { - "message": "Make sure the Fingerprint phrase matches the one below before approving." + "message": "اطمینان حاصل کنید که عبارت اثر انگشت با عبارت زیر مطابقت دارد قبل از تأیید." }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "به گزینه دیگری نیاز دارید؟" }, "fingerprintMatchInfo": { "message": "لطفاً مطمئن شوید که قفل گاوصندوق شما باز است و عبارت اثر انگشت در دستگاه دیگر مطابقت دارد." @@ -2924,13 +2924,13 @@ "message": "عبارت اثر انگشت" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "به‌محض تأیید درخواست، به شما اطلاع داده خواهد شد" }, "needAnotherOption": { "message": "ورود به سیستم با دستگاه باید در تنظیمات برنامه‌ی Bitwarden تنظیم شود. به گزینه دیگری نیاز دارید؟" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "مشاهده همه گزینه‌های ورود به سیستم" }, "viewAllLoginOptions": { "message": "مشاهده همه گزینه‌های ورود به سیستم" @@ -2943,10 +2943,10 @@ "description": "'Character count' describes a feature that displays a number next to each character of the password." }, "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" + "message": "آیا در تلاش برای دسترسی به حساب کاربری خود هستید؟" }, "accessAttemptBy": { - "message": "Access attempt by $EMAIL$", + "message": "تلاش برای دسترسی به سیستم توسط $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2964,10 +2964,10 @@ "message": "زمان" }, "confirmAccess": { - "message": "Confirm access" + "message": "تأیید دسترسی" }, "denyAccess": { - "message": "Deny access" + "message": "دسترسی را رد کن" }, "logInConfirmedForEmailOnDevice": { "message": "ورود به سیستم برای $EMAIL$ در $DEVICE$ تأیید شد", @@ -3004,7 +3004,7 @@ "message": "این درخواست دیگر معتبر نیست." }, "confirmAccessAttempt": { - "message": "Confirm access attempt for $EMAIL$", + "message": "تأیید تلاش برای دسترسی به سیستم برای $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -3016,28 +3016,28 @@ "message": "ورود الزامیست" }, "accountAccessRequested": { - "message": "Account access requested" + "message": "درخواست دسترسی به حساب کاربری دریافت شد" }, "creatingAccountOn": { - "message": "Creating account on" + "message": "در حال ساخت حساب روی" }, "checkYourEmail": { - "message": "Check your email" + "message": "ایمیل خود را چک کنید" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "دنبال کنید لینکی را که در ایمیل فرستاده شده به" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "و به ساختن حساب‌تان ادامه دهید." }, "noEmail": { - "message": "No email?" + "message": "ایمیلی دریافت نکردید؟" }, "goBack": { - "message": "Go back" + "message": "بازگشت به عقب" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "برای ویرایش آدرس ایمیل خود." }, "exposedMasterPassword": { "message": "کلمه عبور اصلی افشا شده" @@ -3052,28 +3052,28 @@ "message": "کلمه عبور ضعیف شناسایی و در یک نقض داده پیدا شد. از یک کلمه عبور قوی و منحصر به فرد برای محافظت از حساب خود استفاده کنید. آیا مطمئنید که می‌خواهید از این کلمه عبور استفاده کنید؟" }, "useThisPassword": { - "message": "Use this password" + "message": "از این کلمه عبور استفاده کن" }, "useThisUsername": { - "message": "Use this username" + "message": "از این نام کاربری استفاده کن" }, "checkForBreaches": { "message": "نقض اطلاعات شناخته شده برای این کلمه عبور را بررسی کنید" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "وارد شده!" }, "important": { "message": "مهم:" }, "accessing": { - "message": "Accessing" + "message": "در حال دسترسی" }, "accessTokenUnableToBeDecrypted": { - "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + "message": "شما خارج شده‌اید زیرا توکن دسترسی شما قابل رمزگشایی نبود. لطفاً برای رفع این مشکل دوباره وارد شوید." }, "refreshTokenSecureStorageRetrievalFailure": { - "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + "message": "شما خارج شده‌اید زیرا توکن تازه‌سازی شما قابل بازیابی نبود. لطفاً برای رفع این مشکل دوباره وارد شوید." }, "masterPasswordHint": { "message": "کلمه عبور اصلی شما در صورت فراموشی قابل بازیابی نیست!" @@ -3088,22 +3088,22 @@ } }, "windowsBiometricUpdateWarning": { - "message": "Bitwarden توصیه می‌کند که تنظیمات بیومتریک خود را به‌روزرسانی کنید تا در اولین باز کردن قفل، به رمز عبور اصلی (یا پین) نیاز داشته باشید. آیا می‌خواهید تنظیمات خود را اکنون به‌روز کنید؟" + "message": "Bitwarden توصیه می‌کند که تنظیمات بیومتریک خود را به‌روزرسانی کنید تا در اولین باز کردن قفل، به کلمه عبور اصلی (یا کد پین) نیاز داشته باشید. آیا می‌خواهید تنظیمات خود را اکنون به‌روز کنید؟" }, "windowsBiometricUpdateWarningTitle": { "message": "به‌روز رسانی تنظیمات توصیه شده" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "این دستگاه را به خاطر بسپار تا ورودهای بعدی بدون مشکل انجام شود" }, "deviceApprovalRequired": { "message": "تأیید دستگاه لازم است. یک روش تأیید انتخاب کنید:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "تأیید دستگاه لازم است" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "یکی از گزینه‌های تأیید زیر را انتخاب کنید" }, "rememberThisDevice": { "message": "این دستگاه را به خاطر بسپار" @@ -3152,34 +3152,34 @@ "message": "ایمیل کاربر وجود ندارد" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "ایمیل کاربر فعال پیدا نشد. در حال خارج کردن شما از سیستم هستیم." }, "deviceTrusted": { "message": "دستگاه مورد اعتماد است" }, "trustOrganization": { - "message": "Trust organization" + "message": "اعتماد به سازمان" }, "trust": { - "message": "Trust" + "message": "اطمینان" }, "doNotTrust": { - "message": "Do not trust" + "message": "اعتماد نکنید" }, "organizationNotTrusted": { - "message": "Organization is not trusted" + "message": "سازمان مورد اعتماد نیست" }, "emergencyAccessTrustWarning": { - "message": "For the security of your account, only confirm if you have granted emergency access to this user and their fingerprint matches what is displayed in their account" + "message": "برای امنیت حساب کاربری شما، فقط در صورتی تأیید کنید که دسترسی اضطراری به این کاربر داده‌اید و اثر انگشت او با آنچه در حسابش نمایش داده شده، مطابقت دارد" }, "orgTrustWarning": { - "message": "For the security of your account, only proceed if you are a member of this organization, have account recovery enabled, and the fingerprint displayed below matches the organization's fingerprint." + "message": "برای امنیت حساب کاربری شما، فقط در صورتی ادامه دهید که عضو این سازمان باشید، بازیابی حساب کاربری را فعال کرده باشید و اثر انگشت نمایش داده شده در زیر با اثر انگشت سازمان مطابقت داشته باشد." }, "orgTrustWarning1": { - "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + "message": "این سازمان دارای سیاست سازمانی است که شما را در بازیابی حساب کاربری ثبت‌نام می‌کند. ثبت‌نام به مدیران سازمان اجازه می‌دهد کلمه عبور شما را تغییر دهند. فقط در صورتی ادامه دهید که این سازمان را می‌شناسید و عبارت اثر انگشت نمایش داده شده در زیر با اثر انگشت سازمان مطابقت دارد." }, "trustUser": { - "message": "Trust user" + "message": "به کاربر اعتماد کنید" }, "inputRequired": { "message": "ورودی ضروری است." @@ -3282,7 +3282,7 @@ "message": "زیرمنو" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "تغییر وضعیت ناوبری کناری" }, "skipToContent": { "message": "پرش به محتوا" @@ -3300,14 +3300,14 @@ "message": "دامنه مستعار" }, "importData": { - "message": "وارد کردن اطلاعات", + "message": "درون ریزی داده", "description": "Used for the desktop menu item and the header of the import dialog" }, "importError": { - "message": "خطای وارد کردن" + "message": "خطای درون ریزی" }, "importErrorDesc": { - "message": "مشکلی با داده‌هایی که سعی کردید وارد کنید وجود داشت. لطفاً خطاهای فهرست شده در زیر را در فایل منبع خود برطرف کرده و دوباره امتحان کنید." + "message": "مشکلی با داده‌هایی که سعی کردید درون ریزی کنید وجود داشت. لطفاً خطاهای فهرست شده در زیر را در پرونده منبع خود برطرف کرده و دوباره امتحان کنید." }, "resolveTheErrorsBelowAndTryAgain": { "message": "خطاهای زیر را برطرف کرده و دوباره امتحان کنید." @@ -3316,10 +3316,10 @@ "message": "توضیحات" }, "importSuccess": { - "message": "داده‌ها با موفقیت وارد شد" + "message": "داده‌ها با موفقیت درون ریزی شد" }, "importSuccessNumberOfItems": { - "message": "$AMOUNT$ مورد وارد شده است.", + "message": "$AMOUNT$ مورد درون ریزی شده است.", "placeholders": { "amount": { "content": "$1", @@ -3331,7 +3331,7 @@ "message": "مجموع" }, "importWarning": { - "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "message": "شما در حال درون ریزی داده به $ORGANIZATION$ هستید. داده‌های شما ممکن است با اعضای این سازمان به اشتراک گذاشته شود. آیا می‌خواهید ادامه دهید؟", "placeholders": { "organization": { "content": "$1", @@ -3340,49 +3340,49 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "خطا در اتصال به سرویس Duo. از روش ورود دو مرحله‌ای دیگری استفاده کنید یا برای دریافت کمک با Duo تماس بگیرید." }, "duoRequiredByOrgForAccount": { - "message": "Duo two-step login is required for your account." + "message": "ورود دو مرحله ای Duo برای حساب کاربری شما لازم است." }, "duoTwoFactorRequiredPageSubtitle": { - "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + "message": "برای حساب کاربری شما ورود دو مرحله‌ای Duo لازم است. مراحل زیر را دنبال کنید تا ورود خود را کامل کنید." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Follow the steps below to finish logging in." + "message": "مراحل زیر را دنبال کنید تا وارد سیستم شوید." }, "followTheStepsBelowToFinishLoggingInWithSecurityKey": { - "message": "Follow the steps below to finish logging in with your security key." + "message": "مراحل زیر را برای کامل کردن ورود با کلید امنیتی خود دنبال کنید." }, "launchDuo": { - "message": "Launch Duo in Browser" + "message": "اجرای Duo در مرورگر" }, "importFormatError": { - "message": "Data is not formatted correctly. Please check your import file and try again." + "message": "داده‌ها به درستی قالب‌بندی نشده‌اند. لطفاً پرونده درون ریزی شده خود را بررسی و دوباره امتحان کنید." }, "importNothingError": { - "message": "چیزی وارد نشد." + "message": "چیزی درون ریزی نشد." }, "importEncKeyError": { - "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + "message": "خطا در رمزگشایی پرونده‌ی درون ریزی شده. کلید رمزگذاری شما با کلید رمزگذاری استفاده شده برای درون ریزی داده‌ها مطابقت ندارد." }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "کلمه عبور پرونده نامعتبر است، لطفاً از کلمه عبوری که هنگام ایجاد پرونده‌ی برون ریزی وارد کردید استفاده کنید." }, "destination": { - "message": "Destination" + "message": "مقصد" }, "learnAboutImportOptions": { - "message": "Learn about your import options" + "message": "درباره گزینه‌های درون ریزی خود بیاموزید" }, "selectImportFolder": { "message": "یک پوشه انتخاب کنید" }, "selectImportCollection": { - "message": "انتخاب یک مجموعه" + "message": "یک مجموعه انتخاب کنید" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "اگر می‌خواهید محتوای پرونده درون ریزی شده به $DESTINATION$ منتقل شود، این گزینه را انتخاب کنید", "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", "placeholders": { "destination": { @@ -3392,25 +3392,25 @@ } }, "importUnassignedItemsError": { - "message": "فایل حاوی موارد اختصاص نیافته است." + "message": "پرونده حاوی موارد اختصاص نیافته است." }, "selectFormat": { - "message": "Select the format of the import file" + "message": "فرمت پرونده‌ی درون ریزی را انتخاب کنید" }, "selectImportFile": { - "message": "انتخاب فایل وارد شده" + "message": "پرونده‌ی درون ریزی را انتخاب کنید" }, "chooseFile": { - "message": "انتخاب فایل" + "message": "انتخاب پرونده" }, "noFileChosen": { - "message": "هیچ فایلی انتخاب نشد" + "message": "هیچ پرونده‌ای انتخاب نشد" }, "orCopyPasteFileContents": { - "message": "یا محتویات فایل وارد شده را کپی/پیست کنید" + "message": "یا محتویات پرونده‌ی درون ریزی را کپی/پیست کنید" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "دستورالعمل‌های $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -3420,120 +3420,120 @@ } }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "درون ریزی گاوصندوق را تأیید کنید" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "این پرونده با کلمه عبور محافظت شده است. لطفاً کلمه عبور پرونده را برای درون ریزی داده‌ها وارد کنید." }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "تأیید کلمه عبور پرونده" }, "exportSuccess": { - "message": "Vault data exported" + "message": "داده‌های گاوصندوق برون ریزی شد" }, "multifactorAuthenticationCancelled": { - "message": "تایید هویت چندمرحله‌ای کنسل شد" + "message": "تایید هویت چند مرحله‌ای کنسل شد" }, "noLastPassDataFound": { - "message": "No LastPass data found" + "message": "هیچ داده‌ای از LastPass یافت نشد" }, "incorrectUsernameOrPassword": { - "message": "نام کاربری یا گذرواژه نادرست است" + "message": "نام کاربری یا کلمه عبور اشتباه است" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "کلمه عبور اشتباه است" }, "incorrectCode": { - "message": "Incorrect code" + "message": "کد اشتباه است" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "کد پین نادرست است" }, "multifactorAuthenticationFailed": { - "message": "Multifactor authentication failed" + "message": "احراز هویت چند مرحله‌ای ناموفق بود" }, "includeSharedFolders": { - "message": "Include shared folders" + "message": "شامل پوشه‌های به‌ اشتراک گذاری‌ شده" }, "lastPassEmail": { - "message": "LastPass Email" + "message": "ایمیل LastPass" }, "importingYourAccount": { - "message": "Importing your account..." + "message": "در حال درون ریزی حساب کاربری شما..." }, "lastPassMFARequired": { - "message": "LastPass multifactor authentication required" + "message": "احراز هویت چند مرحله‌ای LastPass مورد نیاز است" }, "lastPassMFADesc": { - "message": "Enter your one-time passcode from your authentication app" + "message": "کد یک‌بار مصرف خود را از برنامه احراز هویت وارد کنید" }, "lastPassOOBDesc": { - "message": "Approve the login request in your authentication app or enter a one-time passcode." + "message": "درخواست ورود را در برنامه احراز هویت خود تأیید کنید یا یک کد یک‌بار مصرف وارد کنید." }, "passcode": { - "message": "Passcode" + "message": "کد عبور" }, "lastPassMasterPassword": { - "message": "LastPass master password" + "message": "کلمه عبور اصلی LastPass" }, "lastPassAuthRequired": { - "message": "LastPass authentication required" + "message": "احراز هویت LastPass مورد نیاز است" }, "awaitingSSO": { - "message": "Awaiting SSO authentication" + "message": "در انتظار احراز هویت SSO" }, "awaitingSSODesc": { - "message": "Please continue to log in using your company credentials." + "message": "لطفاً برای ورود، از اطلاعات کاربری شرکت خود استفاده کنید." }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "دستورالعمل‌های کامل را در سایت راهنمای ما مشاهده کنید در", "description": "This is followed a by a hyperlink to the help website." }, "importDirectlyFromLastPass": { - "message": "Import directly from LastPass" + "message": "درون ریزی مستقیم از LastPass" }, "importFromCSV": { - "message": "وارد کردن از CSV" + "message": "درون ریزی از CSV" }, "lastPassTryAgainCheckEmail": { - "message": "Try again or look for an email from LastPass to verify it's you." + "message": "دوباره تلاش کنید یا ایمیلی از LastPass را بررسی کنید تا هویت شما تأیید شود." }, "collection": { "message": "مجموعه" }, "lastPassYubikeyDesc": { - "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." + "message": "کلید YubiKey مربوط به حساب کاربری LastPass خود را در درگاه USB کامپیوتر قرار دهید، سپس دکمه آن را لمس کنید." }, "commonImportFormats": { "message": "فرمت‌های رایج", "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "موفقیت آمیز بود" }, "troubleshooting": { - "message": "Troubleshooting" + "message": "عیب یابی" }, "disableHardwareAccelerationRestart": { - "message": "Disable hardware acceleration and restart" + "message": "شتاب‌دهی سخت‌افزاری را غیرفعال کنید و دوباره راه‌اندازی کنید" }, "enableHardwareAccelerationRestart": { - "message": "Enable hardware acceleration and restart" + "message": "شتاب‌دهی سخت‌افزاری را فعال کنید و دوباره راه‌اندازی کنید" }, "removePasskey": { - "message": "Remove passkey" + "message": "حذف کلید عبور" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "کلید عبور حذف شد" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "خطا در اختصاص مجموعه هدف." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "خطا در اختصاص پوشه هدف." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "مشاهده موارد در $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3543,7 +3543,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "بازگشت به $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3553,11 +3553,11 @@ } }, "back": { - "message": "Back", + "message": "بازگشت", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "حذف $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3567,42 +3567,42 @@ } }, "data": { - "message": "Data" + "message": "داده‌" }, "fileSends": { - "message": "File Sends" + "message": "پرونده ارسال‌ها" }, "textSends": { - "message": "Text Sends" + "message": "ارسال‌های متن" }, "ssoError": { - "message": "No free ports could be found for the sso login." + "message": "هیچ پورت رایگانی برای ورود sso یافت نشد." }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "کلمه عبور ایمن ساخته شد! فراموش نکنید کلمه عبور خود را در وب‌سایت نیز به‌روزرسانی کنید." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "از تولید کننده استفاده کنید", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "برای ایجاد یک کلمه عبور قوی و منحصر به فرد", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "biometricsStatusHelptextUnlockNeeded": { - "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + "message": "بازکردن با بیومتریک در دسترس نیست زیرا ابتدا باید بازکردن قفل با کد پین یا کلمه عبور انجام شود." }, "biometricsStatusHelptextHardwareUnavailable": { - "message": "Biometric unlock is currently unavailable." + "message": "باز کردن قفل با بیومتریک در حال حاضر در دسترس نیست." }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "باز کردن قفل با بیومتریک به دلیل پیکربندی نادرست پرونده‌های سیستم در دسترس نیست." }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "باز کردن قفل با بیومتریک به دلیل پیکربندی نادرست پرونده‌های سیستم در دسترس نیست." }, "biometricsStatusHelptextNotEnabledLocally": { - "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "message": "باز کردن قفل با بیومتریک در دسترس نیست زیرا برای $EMAIL$ در برنامه دسکتاپ Bitwarden فعال نشده است.", "placeholders": { "email": { "content": "$1", @@ -3611,156 +3611,165 @@ } }, "biometricsStatusHelptextUnavailableReasonUnknown": { - "message": "Biometric unlock is currently unavailable for an unknown reason." + "message": "باز کردن قفل با بیومتریک در حال حاضر به دلیل نامعلومی در دسترس نیست." }, "itemDetails": { - "message": "Item details" + "message": "جزئیات مورد" }, "itemName": { - "message": "Item name" + "message": "نام مورد" }, "loginCredentials": { - "message": "Login credentials" + "message": "اطلاعات ورود" }, "additionalOptions": { - "message": "Additional options" + "message": "گزینه‌های اضافی" }, "itemHistory": { - "message": "Item history" + "message": "تاریخچه مورد" }, "lastEdited": { - "message": "Last edited" + "message": "آخرین ویرایش" }, "upload": { - "message": "Upload" + "message": "بارگذاری" }, "authorize": { - "message": "Authorize" + "message": "اجازه دادن" }, "deny": { - "message": "Deny" + "message": "رد کن" }, "sshkeyApprovalTitle": { - "message": "Confirm SSH key usage" + "message": "تأیید استفاده از کلید SSH" }, "agentForwardingWarningTitle": { - "message": "Warning: Agent Forwarding" + "message": "هشدار: انتقال عامل" }, "agentForwardingWarningText": { - "message": "This request comes from a remote device that you are logged into" + "message": "این درخواست از دستگاه راه دوری ارسال شده است که شما در آن وارد شده‌اید" }, "sshkeyApprovalMessageInfix": { - "message": "is requesting access to" + "message": "درخواست دسترسی دارد به" }, "sshkeyApprovalMessageSuffix": { - "message": "in order to" + "message": "برای اینکه بتواند" }, "sshActionLogin": { - "message": "authenticate to a server" + "message": "برای احراز هویت به یک سرور" }, "sshActionSign": { - "message": "sign a message" + "message": "امضای یک پیام" }, "sshActionGitSign": { - "message": "sign a git commit" + "message": "امضای یک کامیت گیت" }, "unknownApplication": { - "message": "An application" + "message": "یک برنامه" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "کلید SSH نامعتبر است" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "نوع کلید SSH پشتیبانی نمی‌شود" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "وارد کردن کلید از حافظه موقت" }, "sshKeyImported": { - "message": "SSH key imported successfully" + "message": "کلید SSH با موفقیت وارد شد" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "پرونده در دستگاه ذخیره شد. از بخش بارگیری‌های دستگاه خود مدیریت کنید." }, "allowScreenshots": { - "message": "Allow screen capture" + "message": "اجازه ضبط صفحه" }, "allowScreenshotsDesc": { - "message": "Allow the Bitwarden desktop application to be captured in screenshots and viewed in remote desktop sessions. Disabling this will prevent access on some external displays." + "message": "اجازه دهید برنامه دسکتاپ Bitwarden در اسکرین‌شات‌ها ثبت شده و در جلسات دسکتاپ از راه دور مشاهده شود. غیرفعال کردن این گزینه دسترسی در برخی نمایشگرهای خارجی را مسدود می‌کند." }, "confirmWindowStillVisibleTitle": { - "message": "Confirm window still visible" + "message": "تأیید کنید که پنجره هنوز قابل مشاهده است" }, "confirmWindowStillVisibleContent": { - "message": "Please confirm that the window is still visible." + "message": "لطفاً تأیید کنید که پنجره هنوز قابل مشاهده است." }, "updateBrowserOrDisableFingerprintDialogTitle": { - "message": "Extension update required" + "message": "به‌روزرسانی افزونه لازم است" }, "updateBrowserOrDisableFingerprintDialogMessage": { - "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." + "message": "افزونه مرورگری که استفاده می‌کنید قدیمی است. لطفاً آن را به‌روزرسانی کنید یا اعتبارسنجی اثر انگشت یکپارچه‌سازی مرورگر را در تنظیمات برنامه دسکتاپ غیرفعال کنید." }, "changeAtRiskPassword": { - "message": "Change at-risk password" + "message": "تغییر کلمه عبور در معرض خطر" + }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } }, "move": { - "message": "Move" + "message": "انتقال" }, "newFolder": { - "message": "New folder" + "message": "پوشه جدید" }, "folderName": { - "message": "Folder Name" + "message": "نام پوشه" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "برای تو در تو کردن یک پوشه، نام پوشه والد را وارد کرده و سپس یک “/” اضافه کنید. مثال: Social/Forums" }, "newLoginNudgeTitle": { - "message": "Save time with autofill" + "message": "با پر کردن خودکار در وقت خود صرفه جویی کنید" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "شامل یک", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "وب‌سایت", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "تا این ورود به عنوان پیشنهاد پر کردن خودکار ظاهر شود.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newCardNudgeTitle": { - "message": "Seamless online checkout" + "message": "پرداخت آنلاین بدون وقفه" }, "newCardNudgeBody": { - "message": "With cards, easily autofill payment forms securely and accurately." + "message": "با کارت‌ها، فرم‌های پرداخت را به‌راحتی و با امنیت و دقت پر کنید." }, "newIdentityNudgeTitle": { - "message": "Simplify creating accounts" + "message": "ساخت حساب‌های کاربری را ساده کنید" }, "newIdentityNudgeBody": { - "message": "With identities, quickly autofill long registration or contact forms." + "message": "با هویت‌ها، به سرعت فرم‌های طولانی ثبت‌نام یا تماس را پر کنید." }, "newNoteNudgeTitle": { - "message": "Keep your sensitive data safe" + "message": "اطلاعات حساس خود را ایمن نگه دارید" }, "newNoteNudgeBody": { - "message": "With notes, securely store sensitive data like banking or insurance details." + "message": "با یادداشت‌ها، اطلاعات حساسی مانند جزئیات بانکی یا بیمه را به‌صورت ایمن ذخیره کنید." }, "newSshNudgeTitle": { - "message": "Developer-friendly SSH access" + "message": "دسترسی SSH مناسب برای توسعه‌دهندگان" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "کلیدهای خود را ذخیره کنید و با عامل SSH برای احراز هویت سریع و رمزگذاری‌شده متصل شوید.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Learn more about SSH agent", + "message": "اطلاعات بیشتر درباره عامل SSH را بیاموزید", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" } diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 14cf9c95ffc..bffa90c8a17 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -247,10 +247,10 @@ "message": "Remember SSH authorizations" }, "sshAgentPromptBehaviorAlways": { - "message": "Always" + "message": "Aina" }, "sshAgentPromptBehaviorNever": { - "message": "Never" + "message": "Ei koskaan" }, "sshAgentPromptBehaviorRememberUntilLock": { "message": "Remember until vault is locked" @@ -446,7 +446,7 @@ "message": "Lisää kenttä" }, "editField": { - "message": "Edit field" + "message": "Muokkaa kenttää" }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" @@ -1998,13 +1998,13 @@ "description": "Premium membership" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "Ilmaiset organisaatiot eivät voi käyttää liitteitä" }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "Yksi kenttä vaatii huomiotasi." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ kenttää vaatii huomiotasi.", "placeholders": { "count": { "content": "$1", @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Pääsalasana poistettiin" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ käyttää kertakirjautumista (SSO) itse ylläpidetyllä avainpalvelimella. Organisaation jäsenet eivät enää tarvitse pääsalasanaa kirjautumiseen.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Poistu organisaatiosta" @@ -3158,16 +3158,16 @@ "message": "Laitteeseen luotettu" }, "trustOrganization": { - "message": "Trust organization" + "message": "Luota organisaatioon" }, "trust": { - "message": "Trust" + "message": "Luota" }, "doNotTrust": { - "message": "Do not trust" + "message": "Älä luota" }, "organizationNotTrusted": { - "message": "Organization is not trusted" + "message": "Organisaatio ei ole luotettu" }, "emergencyAccessTrustWarning": { "message": "For the security of your account, only confirm if you have granted emergency access to this user and their fingerprint matches what is displayed in their account" @@ -3179,7 +3179,7 @@ "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." }, "trustUser": { - "message": "Trust user" + "message": "Luota käyttäjään" }, "inputRequired": { "message": "Syöte vaaditaan." @@ -3614,25 +3614,25 @@ "message": "Biometrinen avaus ei ole tällä hetkellä käytettävissä tuntemattomasta syystä." }, "itemDetails": { - "message": "Item details" + "message": "Kohteen tiedot" }, "itemName": { - "message": "Item name" + "message": "Kohteen nimi" }, "loginCredentials": { - "message": "Login credentials" + "message": "Kirjautumistiedot" }, "additionalOptions": { - "message": "Additional options" + "message": "Lisävalinnat" }, "itemHistory": { - "message": "Item history" + "message": "Kohteen historia" }, "lastEdited": { - "message": "Last edited" + "message": "Viimeksi muokattu" }, "upload": { - "message": "Upload" + "message": "Lähetä" }, "authorize": { "message": "Valtuuta" @@ -3703,20 +3703,29 @@ "changeAtRiskPassword": { "message": "Vaihda vaarantunut salasana" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Siirrä" }, "newFolder": { - "message": "New folder" + "message": "Uusi kansio" }, "folderName": { - "message": "Folder Name" + "message": "Kansion nimi" }, "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, "newLoginNudgeTitle": { - "message": "Save time with autofill" + "message": "Säästä aikaa automaattitäytöllä" }, "newLoginNudgeBodyOne": { "message": "Include a", @@ -3724,7 +3733,7 @@ "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "Verkkosivusto", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index b3633cd1694..74c886a98b6 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Tinanggal ang password ng master" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ ay gumagamit ng SSO na may self-hosted key server. Ang isang master password ay hindi na kinakailangan upang mag log in para sa mga miyembro ng organisasyong ito.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Umalis sa organisasyon" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index e94f4837d99..2c53c0652bd 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Mot de passe principal supprimé" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ utilise le SSO avec un serveur de clés auto-hébergé. Un mot de passe principal n'est plus nécessaire aux membres de cette organisation pour se connecter.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Quitter l'organisation" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Changer le mot de passe à risque" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 471d7235945..17495f96785 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 954c50812be..3999caa1bbd 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "הסיסמה הראשית הוסרה" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ משתמש/ת ב־SSO עם שרת מפתחות באירוח עצמי. סיסמה ראשית כבר לא נדרשת כדי להיכנס עבור חברים של ארגון זה.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "עזוב ארגון" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "שנה סיסמה בסיכון" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 10307cc03ca..292e1c4b7d2 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 8bdfcb40534..cf90703a438 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Glavna lozinka uklonjena." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ koristi jedinstvenu prijavu SSO s vlastitim poslužiteljem. Članovima organizacije glavna lozinka više nije potrebna.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Napusti organizaciju" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Promijeni rizičnu lozinku" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index e56b62f9840..f7eb77766c1 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "A mesterjelszó eltávolításra került." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ jelenleg saját tárolású aláíráskulcsú SSO szervert használ. A mesterjelszó a továbbiakban nem szükséges a szervezeti tagsági bejelentkezéshez.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A következő szervezet tagjai számára már nincs szükség mesterjelszóra. Erősítsük meg az alábbi tartományt a szervezet adminisztrátorával." + }, + "organizationName": { + "message": "Szervezet neve" + }, + "keyConnectorDomain": { + "message": "Key Connector tartomány" }, "leaveOrganization": { "message": "Szervezet elhagyása" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Kockázatos jelszó megváltoztatása" }, + "cannotRemoveViewOnlyCollections": { + "message": "Nem távolíthatók el a csak megtekintési engedéllyel bíró gyűjtemények: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Áthelyezés" }, diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 1eeb32fdcc6..70be61f4bf3 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Sandi utama dihapus" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ menggunakan SSO dengan server kunci yang dihosting sendiri. Kata sandi utama tidak lagi diperlukan untuk masuk untuk anggota organisasi ini.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Tinggalkan organisasi" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index db70740d96e..a3dcb13fa76 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Password principale rimossa" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ sta usando SSO con un server self-hosted. Una password principale non è più necessaria per accedere per i membri di questa organizzazione.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lascia organizzazione" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Modifica la password non sicura o esposta" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 47ba77508bc..624a296f32b 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "マスターパスワードを削除しました。" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ は自己ホストの鍵サーバで SSO を使用しています。この組織のメンバーのログインにマスターパスワードは必要ありません。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "組織から脱退する" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "危険なパスワードの変更" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "移動" }, diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 6a2912eed10..45047cd678f 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 471d7235945..17495f96785 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 767cdcf9d03..e74fc479016 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index b7b2a82b4b5..371f4cae1c8 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "마스터 비밀번호가 제거되었습니다." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 조직은 자체 호스팅 키 서버로 SSO를 사용하고 있습니다 이 조직의 멤버들은 로그인할 때에 마스터 비밀번호를 필요로 하지 않습니다.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "조직 나가기" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index 66dcf1da155..f98a5dba2e1 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Pagrindinis slaptažodis pašalintas" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ naudoja SSO su savarankiškai sukurtu raktų serveriu. Pagrindinis slaptažaodis nebėra reikalingas norint šios organizacijos nariams prisijungti.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Palikti organizaciją" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 437aba39714..6c35520605d 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Galvenā parole tika noņemta." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ izmanto vienoto pieteikšanos ar pašmitinātu atslēgu serveri. Tās dalībniekiem vairs nav nepieciešama galvenā parole, lai pieteiktos.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Galvenā parole vairs nav nepieciešama turpmāk minētās apvienības dalībniekiem. Lūgums saskaņot zemāk esošo domēnu ar savas apvienības pārvaldītāju." + }, + "organizationName": { + "message": "Apvienības nosaukums" + }, + "keyConnectorDomain": { + "message": "Key Connector domēns" }, "leaveOrganization": { "message": "Pamest apvienību" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Mainīt riskam pakļautu paroli" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Pārvietot" }, diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 152877fa552..51310719f0a 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 3fcb52bade5..a760acb86ec 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 471d7235945..17495f96785 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 724194808cf..2179311c501 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 5522218037f..3ddf3e44e5f 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Hovedpassordet er fjernet." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ bruker SSO med en selvdrevet nøkkelserver. Et hovedpassord er ikke lenger nødvendig for å logge inn for medlemmer av denne organisasjonen.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Forlat organisasjon" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Flytt" }, diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index a56268aa671..9ae7b7af955 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index cad8a495dbf..b1e04600908 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Hoofdwachtwoord verwijderd." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ gebruikt SSO met een zelf gehoste sleutelserver. Leden van deze organisatie kunnen inloggen zonder hoofdwachtwoord.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Voor leden van de volgende organisatie is een hoofdwachtwoord niet langer nodig. Bevestig het domein hieronder met de beheerder van je organisatie." + }, + "organizationName": { + "message": "Organisatienaam" + }, + "keyConnectorDomain": { + "message": "Key Connector-domein" }, "leaveOrganization": { "message": "Organisatie verlaten" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Risicovol wachtwoord wijzigen" }, + "cannotRemoveViewOnlyCollections": { + "message": "Je kunt verzamelingen niet verwijderen met alleen rechten voor weergeven: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Verplaatsen" }, diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index b3f0f0c5d20..097da5886a6 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index d584cd5d117..a71275e64ac 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 61c438c0425..84ef8f0ca9a 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Hasło główne zostało usunięte" }, - "convertOrganizationEncryptionDesc": { - "message": "Organizacja $ORGANIZATION$ używa jednokrotnego logowania SSO z własnym serwerem kluczy. Użytkownicy nie muszą logować się za pomocą hasła głównego.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Opuść organizację" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Zmień zagrożone hasło" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index e44bb7abc8c..43fa1087988 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Senha mestra removida." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ está usando SSO com um servidor de chaves auto-hospedado. Não é mais necessária uma senha mestra para os membros desta organização entrarem.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Sair da Organização" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Alterar senhas vulneráveis" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Mover" }, diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index ef657f1b658..dc40c91960e 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Palavra-passe mestra removida" }, - "convertOrganizationEncryptionDesc": { - "message": "A $ORGANIZATION$ está a utilizar o SSO com um servidor de chaves auto-hospedado. Já não é necessária uma palavra-passe mestra para iniciar sessão para os membros desta organização.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Já não é necessária uma palavra-passe mestra para os membros da seguinte organização. Por favor, confirme o domínio abaixo com o administrador da sua organização." + }, + "organizationName": { + "message": "Nome da organização" + }, + "keyConnectorDomain": { + "message": "Domínio do Key Connector" }, "leaveOrganization": { "message": "Sair da organização" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Alterar palavra-passe em risco" }, + "cannotRemoveViewOnlyCollections": { + "message": "Não é possível remover coleções com permissões de Apenas visualização: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Mover" }, diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 9024aaa5533..f108d420829 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Parola principală înlăturată" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ folosește SSO cu un server de chei auto-găzduit. Membrii acestei organizații nu mai au nevoie de o parolă principală pentru autentificare.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Părăsire organizație" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 0e58487ae88..52dea4d205c 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -157,7 +157,7 @@ "message": "Код безопасности" }, "identityName": { - "message": "Название личности" + "message": "Название личной информации" }, "company": { "message": "Компания" @@ -2188,7 +2188,7 @@ "message": "Импорт элементов в ваше личное хранилище отключен политикой организации." }, "personalDetails": { - "message": "Личные данные" + "message": "Личная информация" }, "identification": { "message": "Идентификация" @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Мастер-пароль удален." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ использует SSO с собственным сервером ключей. Для авторизации пользователям этой организации больше не требуется мастер-пароль.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Мастер-пароль больше не требуется для членов следующей организации. Пожалуйста, подтвердите указанный ниже домен у администратора вашей организации." + }, + "organizationName": { + "message": "Название организации" + }, + "keyConnectorDomain": { + "message": "Домен соединителя ключей" }, "leaveOrganization": { "message": "Покинуть организацию" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Изменить пароль, подверженный риску" }, + "cannotRemoveViewOnlyCollections": { + "message": "Вы не можете удалить коллекции с правами только на просмотр: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Переместить" }, diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index bb843718e28..03124c345da 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index d84b164e749..3ee4a800a61 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Hlavné heslo bolo odstránené." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ používa SSO s vlastným kľúčovým serverom. Na prihlásenie členov tejto organizácie už nie je potrebné hlavné heslo.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hlavné heslo sa už nevyžaduje pre členov tejto organizácie. Nižšie uvedenú doménu potvrďte u správcu organizácie." + }, + "organizationName": { + "message": "Názov organizácie" + }, + "keyConnectorDomain": { + "message": "Doména Key Connectora" }, "leaveOrganization": { "message": "Opustiť organizáciu" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Zmeniť rizikové heslá" }, + "cannotRemoveViewOnlyCollections": { + "message": "Zbierky, ktoré môžete len zobraziť nemôžete odstrániť: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Presunúť" }, diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 6cc9e6fc197..e1f7dcd578b 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Glavno geslo je bilo odstranjeno." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Zapusti organizacijo" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 7509870b0a0..d13c28ae4da 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -238,22 +238,22 @@ "message": "SSH агент је услуга намењена програмерима која вам омогућава да потпишете SSH захтеве директно из вашег Bitwarden сефа." }, "sshAgentPromptBehavior": { - "message": "Ask for authorization when using SSH agent" + "message": "Затражите ауторизацију када користите SSH агент" }, "sshAgentPromptBehaviorDesc": { - "message": "Choose how to handle SSH-agent authorization requests." + "message": "Изаберите како поднети захтеве за ауторизацију SSH-агента." }, "sshAgentPromptBehaviorHelp": { - "message": "Remember SSH authorizations" + "message": "Запамти SSH дозволе" }, "sshAgentPromptBehaviorAlways": { - "message": "Always" + "message": "Увек" }, "sshAgentPromptBehaviorNever": { - "message": "Never" + "message": "Никада" }, "sshAgentPromptBehaviorRememberUntilLock": { - "message": "Remember until vault is locked" + "message": "Запамти док сеф није блокиран" }, "premiumRequired": { "message": "Потребан Премијум" @@ -446,10 +446,10 @@ "message": "Додај поље" }, "editField": { - "message": "Edit field" + "message": "Уреди поље" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "Да ли сте сигурни да желите да трајно избришете овај прилог?" }, "fieldType": { "message": "Врста поља" @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Главна лозинка уклоњена" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ користи SSO уз сопствени сервер за кључеве. Главна лозинка за пријаву више није неопходна за чланове ове организације.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Главна лозинка више није потребна за чланове следеће организације. Молимо потврдите домен са администратором организације." + }, + "organizationName": { + "message": "Назив организације" + }, + "keyConnectorDomain": { + "message": "Домен конектора кључа" }, "leaveOrganization": { "message": "Напусти организацију" @@ -3614,25 +3614,25 @@ "message": "Биометријско откључавање није доступно из непознатог разлога." }, "itemDetails": { - "message": "Item details" + "message": "Детаљи ставке" }, "itemName": { - "message": "Item name" + "message": "Име ставке" }, "loginCredentials": { - "message": "Login credentials" + "message": "Акредитиве за пријављивање" }, "additionalOptions": { - "message": "Additional options" + "message": "Додатне опције" }, "itemHistory": { - "message": "Item history" + "message": "Историја предмета" }, "lastEdited": { - "message": "Last edited" + "message": "Последња измена" }, "upload": { - "message": "Upload" + "message": "Отпреми" }, "authorize": { "message": "Ауторизуј" @@ -3703,33 +3703,42 @@ "changeAtRiskPassword": { "message": "Променити ризичну лозинку" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Премести" }, "newFolder": { - "message": "New folder" + "message": "Нова фасцикла" }, "folderName": { - "message": "Folder Name" + "message": "Име фасцикле" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Угнездите фасциклу додавањем имена надређене фасцкле праћеног знаком „/“. Пример: Друштвени/Форуми" }, "newLoginNudgeTitle": { "message": "Уштедите време са ауто-пуњењем" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "Укључите", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "Веб сајт", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "тако да се ова пријава појављује као предлог за ауто-пуњење.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, @@ -3755,12 +3764,12 @@ "message": "Лак SSH приступ" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "Чувајте кључеве и повежите се са SSH агент за брзу, шифровану аутентификацију.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Learn more about SSH agent", + "message": "Сазнајте више о SSH агенту", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" } diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index eb0d2dcd547..e4fbc6f5773 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Huvudlösenord togs bort" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ använder SSO med en egen nyckelserver. Ett huvudlösenord krävs inte längre för att logga in för medlemmar i denna organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lämna organisation" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 471d7235945..17495f96785 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index 43aaa697a8a..34669ba21c4 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Master password removed" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index c6a59afda23..43a134abfb0 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Ana parola kaldırıldı" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ kendi barındırdığı bir anahtar sunucusuyla SSO kullanıyor. Bu kuruluşun üyelerinin artık ana parola kullanması gerekmiyor.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Kuruluş adı" + }, + "keyConnectorDomain": { + "message": "Key Connector alan adı" }, "leaveOrganization": { "message": "Kuruluştan ayrıl" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Taşı" }, diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index 156c8610ba2..a4a63da8089 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Головний пароль вилучено" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ використовує SSO з власним сервером ключів. Головний пароль для учасників цієї організації більше не вимагається.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Покинути організацію" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Змінити ризикований пароль" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Перемістити" }, diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 2fd354ed08b..4ab9257691c 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -27,7 +27,7 @@ "message": "Ghi chú bảo mật" }, "typeSshKey": { - "message": "SSH key" + "message": "Khóa SSH" }, "folders": { "message": "Thư mục" @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "Chào mừng bạn trở lại" }, "moveToOrgDesc": { "message": "Chọn một tổ chức mà bạn muốn chuyển mục này đến. Việc chuyển đến một tổ chức sẽ chuyển quyền sở hữu mục này cho tổ chức đó. Bạn sẽ không còn là chủ sở hữu trực tiếp của mục này khi đã chuyển." @@ -181,16 +181,16 @@ "message": "Địa chỉ" }, "sshPrivateKey": { - "message": "Private key" + "message": "Khóa riêng SSH" }, "sshPublicKey": { - "message": "Public key" + "message": "Khóa công khai SSH" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "Mã vân tay SSH" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Loại thuật toán" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -205,55 +205,55 @@ "message": "RSA 4096-Bit" }, "sshKeyGenerated": { - "message": "A new SSH key was generated" + "message": "Khóa SSH mới đã được tạo" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "Mật khẩu bạn đã nhập không chính xác." }, "importSshKey": { - "message": "Import" + "message": "Nhập khóa" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "Xác nhận mật khẩu" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "Nhập mật khẩu cho khóa SSH." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "Nhập mật khẩu" }, "sshAgentUnlockRequired": { - "message": "Please unlock your vault to approve the SSH key request." + "message": "Vui lòng mở khóa kho lưu trữ của bạn để phê duyệt yêu cầu khóa SSH." }, "sshAgentUnlockTimeout": { - "message": "SSH key request timed out." + "message": "Hết thời gian chờ yêu cầu khóa SSH." }, "enableSshAgent": { - "message": "Enable SSH agent" + "message": "Bật SSH agent" }, "enableSshAgentDesc": { - "message": "Enable the SSH agent to sign SSH requests right from your Bitwarden vault." + "message": "Bật SSH agent để ký các yêu cầu SSH ngay từ kho lưu trữ Bitwarden của bạn." }, "enableSshAgentHelp": { - "message": "The SSH agent is a service targeted at developers that allows you to sign SSH requests directly from your Bitwarden vault." + "message": "SSH agent là một dịch vụ hướng tới các nhà phát triển, cho phép bạn ký các yêu cầu SSH trực tiếp từ kho lưu trữ Bitwarden của mình." }, "sshAgentPromptBehavior": { - "message": "Ask for authorization when using SSH agent" + "message": "Yêu cầu cấp quyền khi sử dụng SSH agent" }, "sshAgentPromptBehaviorDesc": { - "message": "Choose how to handle SSH-agent authorization requests." + "message": "Chọn cách xử lý các yêu cầu cấp quyền của SSH-agent." }, "sshAgentPromptBehaviorHelp": { - "message": "Remember SSH authorizations" + "message": "Ghi nhớ các quyền SSH" }, "sshAgentPromptBehaviorAlways": { - "message": "Always" + "message": "Luôn luôn" }, "sshAgentPromptBehaviorNever": { - "message": "Never" + "message": "Không bao giờ" }, "sshAgentPromptBehaviorRememberUntilLock": { - "message": "Remember until vault is locked" + "message": "Ghi nhớ cho đến khi kho lưu trữ bị khóa" }, "premiumRequired": { "message": "Cần có tài khoản cao cấp" @@ -268,17 +268,17 @@ "message": "Lỗi" }, "decryptionError": { - "message": "Decryption error" + "message": "Lỗi giải mã" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden could not decrypt the vault item(s) listed below." + "message": "Bitwarden không thể giải mã các kho lưu trữ được liệt kê dưới đây." }, "contactCSToAvoidDataLossPart1": { - "message": "Contact customer success", + "message": "Liên hệ hỗ trợ khách hàng thành công", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { - "message": "to avoid additional data loss.", + "message": "để tránh mất mát thêm dữ liệu.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "january": { @@ -355,7 +355,7 @@ "message": "Tạo mật khẩu" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Tạo cụm mật khẩu" }, "type": { "message": "Loại" @@ -412,16 +412,16 @@ "message": "Khóa xác thực (TOTP)" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "Khóa xác thực" }, "autofillOptions": { - "message": "Autofill options" + "message": "Tùy chọn tự động điền" }, "websiteUri": { - "message": "Website (URI)" + "message": "Trang web (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "Trang web (URI) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -431,46 +431,46 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "Đã thêm trang web" }, "addWebsite": { - "message": "Add website" + "message": "Thêm trang web" }, "deleteWebsite": { - "message": "Delete website" + "message": "Xóa trang web" }, "owner": { - "message": "Owner" + "message": "Chủ sở hữu" }, "addField": { - "message": "Add field" + "message": "Thêm trường" }, "editField": { - "message": "Edit field" + "message": "Chỉnh sửa trường" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "Bạn có chắc muốn xóa vĩnh viễn tệp đính kèm này không?" }, "fieldType": { - "message": "Field type" + "message": "Loại trường" }, "fieldLabel": { - "message": "Field label" + "message": "Nhãn trường" }, "add": { - "message": "Add" + "message": "Thêm" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "Sử dụng các trường văn bản cho dữ liệu như câu hỏi bảo mật" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "Sử dụng trường ẩn cho dữ liệu nhạy cảm như mật khẩu" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "Dùng các ô tích chọn nếu bạn muốn tự động điền vào ô tích chọn của biểu mẫu, chẳng hạn như ghi nhớ email" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "Sử dụng trường liên kết khi bạn gặp sự cố tự động điền trên một trang web cụ thể." }, "linkedLabelHelpText": { "message": "Enter the the field's html id, name, aria-label, or placeholder." @@ -1184,7 +1184,7 @@ "message": "Unlock with biometrics" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Mở khóa bằng mật khẩu chính" }, "unlock": { "message": "Mở khóa" @@ -1508,13 +1508,13 @@ "message": "Chưa có mật khẩu." }, "clearHistory": { - "message": "Clear history" + "message": "Xóa lịch sử" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Không có gì để hiển thị" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Bạn chưa tạo gì gần đây" }, "undo": { "message": "Hoàn tác" @@ -1591,7 +1591,7 @@ } }, "copySuccessful": { - "message": "Copy Successful" + "message": "Sao chép thành công" }, "errorRefreshingAccessToken": { "message": "Lỗi làm mới khoá truy cập" @@ -1824,13 +1824,13 @@ "message": "Yêu cầu mật khẩu hoặc mã PIN khi mở" }, "requirePasswordWithoutPinOnStart": { - "message": "Require password on app start" + "message": "Yêu cầu mật khẩu khi khởi động ứng dụng" }, "recommendedForSecurity": { "message": "Đề xuất để tăng cường bảo mật." }, "lockWithMasterPassOnRestart1": { - "message": "Lock with master password on restart" + "message": "Khóa bằng mật khẩu chính khi khởi động lại" }, "deleteAccount": { "message": "Xóa tài khoản" @@ -1842,10 +1842,10 @@ "message": "Việc xóa tài khoản là vĩnh viễn và không thể hoàn tác." }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "Không thể xóa tài khoản" }, "cannotDeleteAccountDesc": { - "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." + "message": "Không thể thực hiện hành động này vì tài khoản của bạn thuộc sở hữu của một tổ chức. Liên hệ với quản trị viên tổ chức của bạn để biết thêm chi tiết." }, "accountDeleted": { "message": "Đã xóa tài khoản" @@ -1958,7 +1958,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "trong tổng số $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -1967,10 +1967,10 @@ } }, "cardDetails": { - "message": "Card details" + "message": "Thông tin thẻ" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "Chi tiết của $BRAND$", "placeholders": { "brand": { "content": "$1", @@ -1979,32 +1979,32 @@ } }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Tìm hiểu thêm về các công cụ xác thực" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "Sao chép khóa xác thực (TOTP)" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "Làm cho xác thực 2 bước trở nên liền mạch" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden có thể lưu trữ và điền mã xác thực 2 bước. Hãy sao chép và dán khóa vào trường này." }, "totpHelperWithCapture": { - "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + "message": "Bitwarden có thể lưu trữ và điền mã xác thực 2 bước. Hãy chọn biểu tượng máy ảnh để chụp mã QR xác thực trên trang web này, hoặc sao chép và dán khóa vào trường này." }, "premium": { - "message": "Premium", + "message": "Phiên bản cao cấp", "description": "Premium membership" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "Các tổ chức miễn phí không thể sử dụng tệp đính kèm" }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1 trường cần bạn chú ý." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "$COUNT$ trường cần bạn chú ý.", "placeholders": { "count": { "content": "$1", @@ -2013,10 +2013,10 @@ } }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Thẻ hết hạn" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "Nếu bạn đã gia hạn, hãy cập nhật thông tin thẻ" }, "verificationRequired": { "message": "Yêu cầu xác minh", @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "Đã xóa mật khẩu chính" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ đang sử dụng SSO với khóa máy chủ tự lưu trữ. Mật khẩu chính không còn cần để đăng nhập cho các thành viên của tổ chức này.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Rời khỏi tổ chức" @@ -2606,7 +2606,7 @@ "message": "Đã khóa" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "Kho lưu trữ đã bị khóa" }, "unlocked": { "message": "Đã mở khóa" @@ -3067,7 +3067,7 @@ "message": "Quan trọng:" }, "accessing": { - "message": "Accessing" + "message": "Đang truy cập" }, "accessTokenUnableToBeDecrypted": { "message": "Bạn đã bị đăng xuất do khoá truy cập của bạn không thể giải mã. Vui lòng đăng nhập lại để khắc phục sự cố này." @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 90cdb33fde5..69d5faa5fb7 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -1836,7 +1836,7 @@ "message": "删除账户" }, "deleteAccountDesc": { - "message": "接下来的操作会删除您的账户和所有密码库数据。" + "message": "继续下面的操作以删除您的账户和所有密码库数据。" }, "deleteAccountWarning": { "message": "删除账户是永久性操作,无法撤销!" @@ -2363,7 +2363,7 @@ "message": "读取安全密钥" }, "awaitingSecurityKeyInteraction": { - "message": "等待安全密钥交互……" + "message": "等待安全密钥交互..." }, "hideEmail": { "message": "对接收者隐藏我的电子邮箱地址。" @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "主密码已移除" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 使用自托管密钥服务器 SSO。这个组织的成员登录时将不再需要主密码。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "以下组织的成员不再需要主密码。请与您的组织管理员确认下面的域名。" + }, + "organizationName": { + "message": "组织名称" + }, + "keyConnectorDomain": { + "message": "Key Connector 域名" }, "leaveOrganization": { "message": "退出组织" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "更改有风险的密码" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "移动" }, diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index eeeaf9b152e..6080c3ee0f8 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -2512,14 +2512,14 @@ "removedMasterPassword": { "message": "主密碼已移除" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 使用自我裝載金鑰伺服器 SSO。此組織的成員登入時將不再需要主密碼。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "離開組織" @@ -3703,6 +3703,15 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "cannotRemoveViewOnlyCollections": { + "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "placeholders": { + "collections": { + "content": "$1", + "example": "Work, Personal" + } + } + }, "move": { "message": "Move" }, From 56a3b14583747a499d66960ac5b372ee0857246e Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Fri, 23 May 2025 08:01:25 -0400 Subject: [PATCH 145/245] Introduce eslint errors for risky/circular imports (#14804) * first draft at an idea dependency graph * ignore existing errors * remove conflicting rule regarding internal platform logic in libs * review: allow components to import from platform --- .../messaging/chrome-message.sender.ts | 2 + .../platform/sync/foreground-sync.service.ts | 2 + .../sync/sync-service.listener.spec.ts | 2 + .../utils/from-chrome-runtime-messaging.ts | 2 + .../electron-renderer-message.sender.ts | 2 + .../src/platform/utils/from-ipc-messaging.ts | 2 + eslint.config.mjs | 248 +++++++++++++++++- .../components/collections.component.ts | 2 + .../base-login-via-webauthn.component.ts | 2 + .../environment-selector.component.ts | 2 + .../auth/components/set-password.component.ts | 4 + .../src/auth/components/set-pin.component.ts | 2 + .../src/auth/guards/active-auth.guard.spec.ts | 2 + .../src/auth/guards/active-auth.guard.ts | 2 + ...vice-trust-toast.service.implementation.ts | 2 + .../device-trust-toast.service.spec.ts | 2 + .../angular/src/components/share.component.ts | 2 + libs/angular/src/services/injection-tokens.ts | 2 + .../src/services/jslib-services.module.ts | 8 + .../view-password-history.service.spec.ts | 2 + .../services/view-password-history.service.ts | 2 + .../deprecated-vault-filter.service.ts | 2 + .../vault/components/add-edit.component.ts | 4 + .../src/vault/components/view.component.ts | 2 + .../account-security-nudge.service.ts | 2 + .../empty-vault-nudge.service.ts | 2 + .../src/vault/services/nudges.service.spec.ts | 2 + .../components/collection-filter.component.ts | 2 + .../components/vault-filter.component.ts | 2 + .../services/vault-filter.service.ts | 2 + .../anon-layout-wrapper.component.ts | 2 + .../anon-layout-wrapper.stories.ts | 2 + .../change-password.component.ts | 2 + .../fingerprint-dialog.component.ts | 2 + .../src/angular/icons/bitwarden-logo.icon.ts | 2 + .../angular/icons/bitwarden-shield.icon.ts | 2 + .../angular/icons/device-verification.icon.ts | 2 + libs/auth/src/angular/icons/devices.icon.ts | 2 + libs/auth/src/angular/icons/lock.icon.ts | 2 + .../icons/registration-check-email.icon.ts | 2 + .../icons/registration-expired-link.icon.ts | 2 + .../icons/registration-lock-alt.icon.ts | 2 + .../icons/registration-user-add.icon.ts | 2 + libs/auth/src/angular/icons/sso-key.icon.ts | 2 + .../two-factor-auth-authenticator.icon.ts | 2 + .../two-factor-auth-duo.icon.ts | 2 + .../two-factor-auth-email.icon.ts | 2 + .../two-factor-auth-security-key.icon.ts | 2 + .../two-factor-auth-webauthn.icon.ts | 2 + .../two-factor-auth-yubico.icon.ts | 2 + .../angular/icons/two-factor-timeout.icon.ts | 2 + libs/auth/src/angular/icons/user-lock.icon.ts | 2 + ...erification-biometrics-fingerprint.icon.ts | 2 + libs/auth/src/angular/icons/vault.icon.ts | 2 + libs/auth/src/angular/icons/wave.icon.ts | 2 + .../input-password.component.ts | 2 + .../input-password/input-password.stories.ts | 2 + .../login-approval.component.spec.ts | 2 + .../login-approval.component.ts | 2 + .../login-decryption-options.component.ts | 2 + .../login-via-auth-request.component.ts | 2 + .../login-secondary-content.component.ts | 2 + .../auth/src/angular/login/login.component.ts | 2 + .../new-device-verification.component.ts | 2 + .../password-callout.component.ts | 2 + .../password-callout.stories.ts | 2 + .../password-hint/password-hint.component.ts | 2 + .../registration-env-selector.component.ts | 2 + .../registration-finish.component.ts | 2 + .../registration-link-expired.component.ts | 2 + .../registration-start-secondary.component.ts | 2 + .../registration-start.component.ts | 2 + .../registration-start.stories.ts | 2 + ...self-hosted-env-config-dialog.component.ts | 2 + .../default-set-password-jit.service.spec.ts | 2 + .../default-set-password-jit.service.ts | 2 + libs/auth/src/angular/sso/sso.component.ts | 2 + ...two-factor-auth-authenticator.component.ts | 2 + .../two-factor-auth-duo.component.ts | 2 + .../two-factor-auth-email.component.ts | 2 + .../two-factor-auth-webauthn.component.ts | 2 + .../two-factor-auth-yubikey.component.ts | 2 + .../two-factor-auth.component.spec.ts | 2 + .../two-factor-auth.component.ts | 2 + .../two-factor-options.component.ts | 2 + .../user-verification-dialog.component.ts | 2 + .../user-verification-dialog.types.ts | 2 + .../user-verification-form-input.component.ts | 2 + .../vault-timeout-input.component.ts | 2 + libs/common/src/abstractions/api.service.ts | 2 + .../response/organization-export.response.ts | 2 + .../provider-user-bulk-public-key.response.ts | 2 + .../registration/register-finish.request.ts | 2 + .../models/request/set-password.request.ts | 2 + ...update-tde-offboarding-password.request.ts | 2 + .../request/update-temp-password.request.ts | 2 + .../response/identity-token.response.ts | 2 + .../auth/models/response/prelogin.response.ts | 2 + .../response/protected-device.response.ts | 2 + .../src/auth/services/auth.service.spec.ts | 2 + libs/common/src/auth/services/auth.service.ts | 2 + .../master-password-api.service.spec.ts | 2 + ...-enrollment.service.implementation.spec.ts | 4 + ...reset-enrollment.service.implementation.ts | 4 + .../src/auth/services/token.service.spec.ts | 2 + .../common/src/auth/services/token.service.ts | 2 + .../user-verification.service.spec.ts | 4 + .../user-verification.service.ts | 4 + .../webauthn-login.service.spec.ts | 2 + .../webauthn-login/webauthn-login.service.ts | 2 + libs/common/src/auth/types/verification.ts | 2 + .../organization-billing.service.spec.ts | 2 + .../services/organization-billing.service.ts | 2 + .../device-trust.service.implementation.ts | 4 + .../services/device-trust.service.spec.ts | 4 + .../models/set-key-connector-key.request.ts | 2 + .../services/key-connector.service.spec.ts | 2 + .../services/key-connector.service.ts | 4 + .../default-process-reload.service.ts | 4 + .../vault-timeout-settings.service.spec.ts | 4 + .../vault-timeout-settings.service.ts | 4 + .../services/vault-timeout.service.spec.ts | 6 + .../services/vault-timeout.service.ts | 6 + .../export/collection-with-id.export.ts | 2 + .../src/models/export/collection.export.ts | 2 + .../import-organization-ciphers.request.ts | 2 + libs/common/src/models/request/kdf.request.ts | 2 + .../abstractions/key-generation.service.ts | 2 + libs/common/src/platform/misc/utils.ts | 2 + .../platform/models/domain/enc-string.spec.ts | 2 + .../default-notifications.service.spec.ts | 2 + .../internal/default-notifications.service.ts | 2 + .../platform/services/container.service.ts | 2 + .../services/key-generation.service.spec.ts | 2 + .../services/key-generation.service.ts | 2 + .../services/sdk/default-sdk.service.spec.ts | 2 + .../services/sdk/default-sdk.service.ts | 2 + .../user-auto-unlock-key.service.spec.ts | 2 + .../services/user-auto-unlock-key.service.ts | 2 + .../src/platform/sync/core-sync.service.ts | 2 + .../sync/default-sync.service.spec.ts | 6 + .../src/platform/sync/default-sync.service.ts | 4 + .../common/src/platform/sync/sync.response.ts | 2 + libs/common/src/services/api.service.spec.ts | 2 + libs/common/src/services/api.service.ts | 4 + ...-service-legacy-encryptor-provider.spec.ts | 2 + .../key-service-legacy-encryptor-provider.ts | 2 + .../src/tools/send/models/domain/send.spec.ts | 2 + .../send/services/send.service.abstraction.ts | 2 + .../tools/send/services/send.service.spec.ts | 2 + .../src/tools/send/services/send.service.ts | 2 + .../src/vault/abstractions/cipher.service.ts | 2 + .../folder/folder.service.abstraction.ts | 2 + .../vault/models/domain/attachment.spec.ts | 2 + .../src/vault/models/domain/cipher.spec.ts | 2 + .../cipher-authorization.service.spec.ts | 2 + .../services/cipher-authorization.service.ts | 2 + .../src/vault/services/cipher.service.spec.ts | 2 + .../src/vault/services/cipher.service.ts | 2 + .../services/folder/folder.service.spec.ts | 2 + .../vault/services/folder/folder.service.ts | 2 + .../src/components/import.component.ts | 2 + libs/importer/src/importers/base-importer.ts | 2 + .../bitwarden/bitwarden-json-importer.ts | 2 + .../src/importers/padlock-csv-importer.ts | 2 + .../src/importers/passpack-csv-importer.ts | 2 + libs/importer/src/models/import-result.ts | 2 + .../import-collection.service.abstraction.ts | 2 + .../services/import.service.abstraction.ts | 2 + .../src/services/import.service.spec.ts | 2 + libs/importer/src/services/import.service.ts | 2 + libs/key-management/src/key.service.spec.ts | 2 + libs/key-management/src/key.service.ts | 2 + .../cipher-form-config.service.ts | 2 + .../src/cipher-form/cipher-form.stories.ts | 2 + .../item-details-section.component.spec.ts | 2 + .../item-details-section.component.ts | 2 + .../default-cipher-form-config.service.ts | 2 + .../src/cipher-view/cipher-view.component.ts | 2 + .../item-details-v2.component.spec.ts | 2 + .../item-details/item-details-v2.component.ts | 2 + .../assign-collections.component.ts | 2 + 182 files changed, 652 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/platform/messaging/chrome-message.sender.ts b/apps/browser/src/platform/messaging/chrome-message.sender.ts index 4fc9c22e26b..c0736f2456e 100644 --- a/apps/browser/src/platform/messaging/chrome-message.sender.ts +++ b/apps/browser/src/platform/messaging/chrome-message.sender.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CommandDefinition, MessageSender } from "@bitwarden/common/platform/messaging"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { getCommand } from "@bitwarden/common/platform/messaging/internal"; type ErrorHandler = (logger: LogService, command: string) => void; diff --git a/apps/browser/src/platform/sync/foreground-sync.service.ts b/apps/browser/src/platform/sync/foreground-sync.service.ts index ce776f53685..2ac75bbec2c 100644 --- a/apps/browser/src/platform/sync/foreground-sync.service.ts +++ b/apps/browser/src/platform/sync/foreground-sync.service.ts @@ -13,6 +13,8 @@ import { } from "@bitwarden/common/platform/messaging"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { StateProvider } from "@bitwarden/common/platform/state"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CoreSyncService } from "@bitwarden/common/platform/sync/internal"; import { SyncOptions } from "@bitwarden/common/platform/sync/sync.service"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; diff --git a/apps/browser/src/platform/sync/sync-service.listener.spec.ts b/apps/browser/src/platform/sync/sync-service.listener.spec.ts index 9682e2cdb57..dc0674a7ae5 100644 --- a/apps/browser/src/platform/sync/sync-service.listener.spec.ts +++ b/apps/browser/src/platform/sync/sync-service.listener.spec.ts @@ -3,6 +3,8 @@ import { Subject, firstValueFrom } from "rxjs"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { tagAsExternal } from "@bitwarden/common/platform/messaging/helpers"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; diff --git a/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts b/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts index ebc01ad86fa..9489c5f2a4e 100644 --- a/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts +++ b/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts @@ -1,6 +1,8 @@ import { map, share } from "rxjs"; import { Message } from "@bitwarden/common/platform/messaging"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { tagAsExternal } from "@bitwarden/common/platform/messaging/internal"; import { fromChromeEvent } from "../browser/from-chrome-event"; diff --git a/apps/desktop/src/platform/services/electron-renderer-message.sender.ts b/apps/desktop/src/platform/services/electron-renderer-message.sender.ts index 109a52a8d8f..a70542e7b20 100644 --- a/apps/desktop/src/platform/services/electron-renderer-message.sender.ts +++ b/apps/desktop/src/platform/services/electron-renderer-message.sender.ts @@ -1,4 +1,6 @@ import { MessageSender, CommandDefinition } from "@bitwarden/common/platform/messaging"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { getCommand } from "@bitwarden/common/platform/messaging/internal"; export class ElectronRendererMessageSender implements MessageSender { diff --git a/apps/desktop/src/platform/utils/from-ipc-messaging.ts b/apps/desktop/src/platform/utils/from-ipc-messaging.ts index cdefbf5c506..f9c01c9487f 100644 --- a/apps/desktop/src/platform/utils/from-ipc-messaging.ts +++ b/apps/desktop/src/platform/utils/from-ipc-messaging.ts @@ -1,6 +1,8 @@ import { fromEventPattern, share } from "rxjs"; import { Message } from "@bitwarden/common/platform/messaging"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { tagAsExternal } from "@bitwarden/common/platform/messaging/internal"; /** diff --git a/eslint.config.mjs b/eslint.config.mjs index 7928224dc00..aa0b475da0b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -278,14 +278,254 @@ export default tseslint.config( ]), }, }, - - /// Team overrides + /// Bandaids for keeping existing circular dependencies from getting worse and new ones from being created + /// Will be removed after Nx is implemented and existing circular dependencies are removed. { - files: ["**/src/platform/**/*.ts"], + files: ["libs/common/src/**/*.ts"], rules: { - "no-restricted-imports": buildNoRestrictedImports([], true), + "no-restricted-imports": buildNoRestrictedImports([ + // Common is at the base level - should not import from other libs except shared + "@bitwarden/admin-console", + "@bitwarden/angular", + "@bitwarden/auth", + "@bitwarden/billing", + "@bitwarden/components", + "@bitwarden/importer", + "@bitwarden/key-management", + "@bitwarden/key-management-ui", + "@bitwarden/node", + "@bitwarden/platform", + "@bitwarden/tools", + "@bitwarden/ui", + "@bitwarden/vault", + ]), }, }, + { + files: ["libs/shared/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Shared shouldnt have deps + "@bitwarden/admin-console", + "@bitwarden/angular", + "@bitwarden/auth", + "@bitwarden/billing", + "@bitwarden/common", + "@bitwarden/components", + "@bitwarden/importer", + "@bitwarden/key-management", + "@bitwarden/key-management-ui", + "@bitwarden/node", + "@bitwarden/platform", + "@bitwarden/tools", + "@bitwarden/ui", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/auth/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Auth can only depend on common, shared, angular, node, platform, eslint + "@bitwarden/admin-console", + "@bitwarden/billing", + "@bitwarden/components", + "@bitwarden/importer", + "@bitwarden/key-management-ui", + "@bitwarden/tools", + "@bitwarden/ui", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/key-management/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Key management can depend on common, node, angular, components, eslint, platform, ui + "@bitwarden/auth", + "@bitwarden/admin-console", + "@bitwarden/billing", + "@bitwarden/importer", + "@bitwarden/key-management-ui", + "@bitwarden/tools", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/billing/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Billing can depend on auth, common, angular, components, eslint, node, platform, ui + "@bitwarden/admin-console", + "@bitwarden/importer", + "@bitwarden/key-management-ui", + "@bitwarden/tools", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/components/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Components can depend on common, shared + "@bitwarden/admin-console", + "@bitwarden/auth", + "@bitwarden/billing", + "@bitwarden/eslint", + "@bitwarden/importer", + "@bitwarden/key-management-ui", + "@bitwarden/node", + "@bitwarden/tools", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/ui/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // UI can depend on common, shared, auth + "@bitwarden/admin-console", + "@bitwarden/billing", + "@bitwarden/importer", + "@bitwarden/key-management-ui", + "@bitwarden/node", + "@bitwarden/platform", + "@bitwarden/tools", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/key-management-ui/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Key-management-ui can depend on key-management, common, angular, shared, auth, components, ui, eslint + "@bitwarden/admin-console", + "@bitwarden/billing", + "@bitwarden/importer", + "@bitwarden/node", + "@bitwarden/platform", + "@bitwarden/tools", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/angular/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Angular can depend on common, shared, components, ui + "@bitwarden/admin-console", + "@bitwarden/auth", + "@bitwarden/billing", + "@bitwarden/importer", + "@bitwarden/key-management-ui", + "@bitwarden/node", + "@bitwarden/platform", + "@bitwarden/tools", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/vault/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Vault can depend on most libs + "@bitwarden/admin-console", + "@bitwarden/importer", + "@bitwarden/tools", + ]), + }, + }, + { + files: ["libs/admin-console/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Admin console can depend on all libs + ]), + }, + }, + { + files: ["libs/tools/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Tools can depend on most libs + "@bitwarden/admin-console", + ]), + }, + }, + { + files: ["libs/platform/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Platform cant depend on most libs + "@bitwarden/admin-console", + "@bitwarden/auth", + "@bitwarden/billing", + "@bitwarden/importer", + "@bitwarden/key-management", + "@bitwarden/key-management-ui", + "@bitwarden/tools", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/importer/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Importer can depend on most libs but not other domain libs + "@bitwarden/admin-console", + "@bitwarden/tools", + ]), + }, + }, + { + files: ["libs/eslint/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // ESLint should not depend on app code + "@bitwarden/admin-console", + "@bitwarden/angular", + "@bitwarden/auth", + "@bitwarden/billing", + "@bitwarden/components", + "@bitwarden/importer", + "@bitwarden/key-management", + "@bitwarden/key-management-ui", + "@bitwarden/node", + "@bitwarden/platform", + "@bitwarden/tools", + "@bitwarden/ui", + "@bitwarden/vault", + ]), + }, + }, + { + files: ["libs/node/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + // Node can depend on common, shared, auth + "@bitwarden/admin-console", + "@bitwarden/angular", + "@bitwarden/components", + "@bitwarden/importer", + "@bitwarden/key-management-ui", + "@bitwarden/platform", + "@bitwarden/tools", + "@bitwarden/ui", + "@bitwarden/vault", + ]), + }, + }, + + /// Team overrides { files: [ "apps/cli/src/admin-console/**/*.ts", diff --git a/libs/angular/src/admin-console/components/collections.component.ts b/libs/angular/src/admin-console/components/collections.component.ts index 8ae90705f92..9d36ab4619f 100644 --- a/libs/angular/src/admin-console/components/collections.component.ts +++ b/libs/angular/src/admin-console/components/collections.component.ts @@ -3,6 +3,8 @@ import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { firstValueFrom, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; diff --git a/libs/angular/src/auth/components/base-login-via-webauthn.component.ts b/libs/angular/src/auth/components/base-login-via-webauthn.component.ts index 5d30fc997dc..53e29d4d940 100644 --- a/libs/angular/src/auth/components/base-login-via-webauthn.component.ts +++ b/libs/angular/src/auth/components/base-login-via-webauthn.component.ts @@ -4,6 +4,8 @@ import { Directive, OnInit } from "@angular/core"; import { Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LoginSuccessHandlerService } from "@bitwarden/auth/common"; import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { WebAuthnLoginCredentialAssertionView } from "@bitwarden/common/auth/models/view/webauthn-login/webauthn-login-credential-assertion.view"; diff --git a/libs/angular/src/auth/components/environment-selector.component.ts b/libs/angular/src/auth/components/environment-selector.component.ts index b61feedd306..1831e513301 100644 --- a/libs/angular/src/auth/components/environment-selector.component.ts +++ b/libs/angular/src/auth/components/environment-selector.component.ts @@ -4,6 +4,8 @@ import { Component, EventEmitter, Output, Input, OnInit, OnDestroy } from "@angu import { ActivatedRoute } from "@angular/router"; import { Observable, map, Subject, takeUntil } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { SelfHostedEnvConfigDialogComponent } from "@bitwarden/auth/angular"; import { EnvironmentService, diff --git a/libs/angular/src/auth/components/set-password.component.ts b/libs/angular/src/auth/components/set-password.component.ts index 230be90b7a4..53f6abaa33c 100644 --- a/libs/angular/src/auth/components/set-password.component.ts +++ b/libs/angular/src/auth/components/set-password.component.ts @@ -5,10 +5,14 @@ import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom, of } from "rxjs"; import { filter, first, switchMap, tap } from "rxjs/operators"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserApiService, OrganizationUserResetPasswordEnrollmentRequest, } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; diff --git a/libs/angular/src/auth/components/set-pin.component.ts b/libs/angular/src/auth/components/set-pin.component.ts index d2fceb34bb9..fba6ce2da86 100644 --- a/libs/angular/src/auth/components/set-pin.component.ts +++ b/libs/angular/src/auth/components/set-pin.component.ts @@ -4,6 +4,8 @@ import { Directive, OnInit } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { firstValueFrom } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; diff --git a/libs/angular/src/auth/guards/active-auth.guard.spec.ts b/libs/angular/src/auth/guards/active-auth.guard.spec.ts index c3417b9d41d..566b2abc72a 100644 --- a/libs/angular/src/auth/guards/active-auth.guard.spec.ts +++ b/libs/angular/src/auth/guards/active-auth.guard.spec.ts @@ -5,6 +5,8 @@ import { RouterTestingModule } from "@angular/router/testing"; import { MockProxy, mock } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/libs/angular/src/auth/guards/active-auth.guard.ts b/libs/angular/src/auth/guards/active-auth.guard.ts index 56213bbd979..83c648ae110 100644 --- a/libs/angular/src/auth/guards/active-auth.guard.ts +++ b/libs/angular/src/auth/guards/active-auth.guard.ts @@ -2,6 +2,8 @@ import { inject } from "@angular/core"; import { CanActivateFn, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/libs/angular/src/auth/services/device-trust-toast.service.implementation.ts b/libs/angular/src/auth/services/device-trust-toast.service.implementation.ts index 4013cf8df96..31e90548a7a 100644 --- a/libs/angular/src/auth/services/device-trust-toast.service.implementation.ts +++ b/libs/angular/src/auth/services/device-trust-toast.service.implementation.ts @@ -1,5 +1,7 @@ import { merge, Observable, tap } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/libs/angular/src/auth/services/device-trust-toast.service.spec.ts b/libs/angular/src/auth/services/device-trust-toast.service.spec.ts index 96b7db9d0ce..10ad3203b90 100644 --- a/libs/angular/src/auth/services/device-trust-toast.service.spec.ts +++ b/libs/angular/src/auth/services/device-trust-toast.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { EMPTY, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/libs/angular/src/components/share.component.ts b/libs/angular/src/components/share.component.ts index 198cc7dc3a5..51827bfb9f2 100644 --- a/libs/angular/src/components/share.component.ts +++ b/libs/angular/src/components/share.component.ts @@ -3,6 +3,8 @@ import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { firstValueFrom, map, Observable, Subject, takeUntil } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index d82ff021962..4c29abe680a 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Observable, Subject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; import { ClientType } from "@bitwarden/common/enums"; import { VaultTimeout } from "@bitwarden/common/key-management/vault-timeout"; diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index a8638efba18..08bcaa2165c 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -3,12 +3,16 @@ import { ErrorHandler, LOCALE_ID, NgModule } from "@angular/core"; import { Subject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, DefaultCollectionService, DefaultOrganizationUserApiService, OrganizationUserApiService, } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AnonLayoutWrapperDataService, DefaultAnonLayoutWrapperDataService, @@ -30,6 +34,8 @@ import { ChangePasswordService, DefaultChangePasswordService, } from "@bitwarden/auth/angular"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AuthRequestApiService, AuthRequestService, @@ -316,6 +322,8 @@ import { UserAsymmetricKeysRegenerationService, } from "@bitwarden/key-management"; import { SafeInjectionToken } from "@bitwarden/ui-common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PasswordRepromptService } from "@bitwarden/vault"; import { IndividualVaultExportService, diff --git a/libs/angular/src/services/view-password-history.service.spec.ts b/libs/angular/src/services/view-password-history.service.spec.ts index dec2b25b190..121aa7dc472 100644 --- a/libs/angular/src/services/view-password-history.service.spec.ts +++ b/libs/angular/src/services/view-password-history.service.spec.ts @@ -3,6 +3,8 @@ import { TestBed } from "@angular/core/testing"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { openPasswordHistoryDialog } from "@bitwarden/vault"; import { VaultViewPasswordHistoryService } from "./view-password-history.service"; diff --git a/libs/angular/src/services/view-password-history.service.ts b/libs/angular/src/services/view-password-history.service.ts index 88ca4d37287..ab4d6d4ddf1 100644 --- a/libs/angular/src/services/view-password-history.service.ts +++ b/libs/angular/src/services/view-password-history.service.ts @@ -3,6 +3,8 @@ import { Injectable } from "@angular/core"; import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { openPasswordHistoryDialog } from "@bitwarden/vault"; /** diff --git a/libs/angular/src/vault/abstractions/deprecated-vault-filter.service.ts b/libs/angular/src/vault/abstractions/deprecated-vault-filter.service.ts index 0a7d6397a04..3e82641fe90 100644 --- a/libs/angular/src/vault/abstractions/deprecated-vault-filter.service.ts +++ b/libs/angular/src/vault/abstractions/deprecated-vault-filter.service.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Observable } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index b04adc1fdfb..8175372cae5 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -4,6 +4,8 @@ import { DatePipe } from "@angular/common"; import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { concatMap, firstValueFrom, map, Observable, Subject, switchMap, takeUntil } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; @@ -40,6 +42,8 @@ import { SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { generate_ssh_key } from "@bitwarden/sdk-internal"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PasswordRepromptService, SshImportPromptService } from "@bitwarden/vault"; @Directive() diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index fd3f92c6c73..ac14c092490 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -54,6 +54,8 @@ import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cip import { TotpInfo } from "@bitwarden/common/vault/services/totp.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PasswordRepromptService } from "@bitwarden/vault"; const BroadcasterSubscriptionId = "BaseViewComponent"; diff --git a/libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts b/libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts index 862beb333d4..30bbd153c5e 100644 --- a/libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts +++ b/libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts @@ -3,6 +3,8 @@ import { Observable, combineLatest, from, of } from "rxjs"; import { catchError, map } from "rxjs/operators"; import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts b/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts index a0e25aa841c..d579f8b1bb2 100644 --- a/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts +++ b/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts @@ -1,6 +1,8 @@ import { inject, Injectable } from "@angular/core"; import { combineLatest, Observable, of, switchMap } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; diff --git a/libs/angular/src/vault/services/nudges.service.spec.ts b/libs/angular/src/vault/services/nudges.service.spec.ts index 4edd57f5428..30e2ada6007 100644 --- a/libs/angular/src/vault/services/nudges.service.spec.ts +++ b/libs/angular/src/vault/services/nudges.service.spec.ts @@ -2,6 +2,8 @@ import { TestBed } from "@angular/core/testing"; import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; diff --git a/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts b/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts index d104026f2f6..feaaf74c96d 100644 --- a/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts +++ b/libs/angular/src/vault/vault-filter/components/collection-filter.component.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Directive, EventEmitter, Input, Output } from "@angular/core"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node"; diff --git a/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts b/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts index 0ce63c03f61..83304f8eae9 100644 --- a/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts +++ b/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts @@ -3,6 +3,8 @@ import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { firstValueFrom, Observable } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node"; diff --git a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts index 6c3ac21b162..9e3312b38ef 100644 --- a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts +++ b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts @@ -3,6 +3,8 @@ import { Injectable } from "@angular/core"; import { firstValueFrom, from, map, mergeMap, Observable, switchMap, take } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts index 04dc3b6dfd2..17f5ec8e3c6 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts @@ -6,6 +6,8 @@ import { Subject, filter, switchMap, takeUntil, tap } from "rxjs"; import { AnonLayoutComponent } from "@bitwarden/auth/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { Icon, Translation } from "@bitwarden/components"; import { AnonLayoutWrapperDataService } from "./anon-layout-wrapper-data.service"; diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts index 9f504c75d29..f106f9ee0dc 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts @@ -15,6 +15,8 @@ import { Environment, } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { ButtonModule } from "@bitwarden/components"; // FIXME: remove `/apps` import from `/libs` diff --git a/libs/auth/src/angular/change-password/change-password.component.ts b/libs/auth/src/angular/change-password/change-password.component.ts index 51c4d03d16f..86b7c884e92 100644 --- a/libs/auth/src/angular/change-password/change-password.component.ts +++ b/libs/auth/src/angular/change-password/change-password.component.ts @@ -8,6 +8,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { UserId } from "@bitwarden/common/types/guid"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { ToastService } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; diff --git a/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts b/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts index d8d76930302..17d7b343db9 100644 --- a/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts +++ b/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts @@ -1,6 +1,8 @@ import { Component, Inject } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DIALOG_DATA, ButtonModule, DialogModule, DialogService } from "@bitwarden/components"; export type FingerprintDialogData = { diff --git a/libs/auth/src/angular/icons/bitwarden-logo.icon.ts b/libs/auth/src/angular/icons/bitwarden-logo.icon.ts index 2a1ae48526b..2df07c45ff9 100644 --- a/libs/auth/src/angular/icons/bitwarden-logo.icon.ts +++ b/libs/auth/src/angular/icons/bitwarden-logo.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const BitwardenLogo = svgIcon` diff --git a/libs/auth/src/angular/icons/bitwarden-shield.icon.ts b/libs/auth/src/angular/icons/bitwarden-shield.icon.ts index 86e3a0bb1b2..f40dc97e5ee 100644 --- a/libs/auth/src/angular/icons/bitwarden-shield.icon.ts +++ b/libs/auth/src/angular/icons/bitwarden-shield.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const BitwardenShield = svgIcon` diff --git a/libs/auth/src/angular/icons/device-verification.icon.ts b/libs/auth/src/angular/icons/device-verification.icon.ts index b1be4efdfb3..6c5313a8705 100644 --- a/libs/auth/src/angular/icons/device-verification.icon.ts +++ b/libs/auth/src/angular/icons/device-verification.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const DeviceVerificationIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/devices.icon.ts b/libs/auth/src/angular/icons/devices.icon.ts index 54acea5b087..dd268f0e6e2 100644 --- a/libs/auth/src/angular/icons/devices.icon.ts +++ b/libs/auth/src/angular/icons/devices.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const DevicesIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/lock.icon.ts b/libs/auth/src/angular/icons/lock.icon.ts index 198733d0dca..43ea2509e19 100644 --- a/libs/auth/src/angular/icons/lock.icon.ts +++ b/libs/auth/src/angular/icons/lock.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const LockIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/registration-check-email.icon.ts b/libs/auth/src/angular/icons/registration-check-email.icon.ts index 6f7dd6a2d63..d32964d8cb1 100644 --- a/libs/auth/src/angular/icons/registration-check-email.icon.ts +++ b/libs/auth/src/angular/icons/registration-check-email.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const RegistrationCheckEmailIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/registration-expired-link.icon.ts b/libs/auth/src/angular/icons/registration-expired-link.icon.ts index 3323c7f0b2b..099cf16d1d8 100644 --- a/libs/auth/src/angular/icons/registration-expired-link.icon.ts +++ b/libs/auth/src/angular/icons/registration-expired-link.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const RegistrationExpiredLinkIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/registration-lock-alt.icon.ts b/libs/auth/src/angular/icons/registration-lock-alt.icon.ts index 511f9710dc6..d312e909413 100644 --- a/libs/auth/src/angular/icons/registration-lock-alt.icon.ts +++ b/libs/auth/src/angular/icons/registration-lock-alt.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const RegistrationLockAltIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/registration-user-add.icon.ts b/libs/auth/src/angular/icons/registration-user-add.icon.ts index 69240cd0298..4f68453639d 100644 --- a/libs/auth/src/angular/icons/registration-user-add.icon.ts +++ b/libs/auth/src/angular/icons/registration-user-add.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const RegistrationUserAddIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/sso-key.icon.ts b/libs/auth/src/angular/icons/sso-key.icon.ts index 38ae8a66525..e00c3555906 100644 --- a/libs/auth/src/angular/icons/sso-key.icon.ts +++ b/libs/auth/src/angular/icons/sso-key.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const SsoKeyIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts index daef1f94dca..4961e7207cb 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const TwoFactorAuthAuthenticatorIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-duo.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-duo.icon.ts index c81433d0fc1..a64027c5fba 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-duo.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-duo.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const TwoFactorAuthDuoIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts index 833ab3f8e98..380bc16a738 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const TwoFactorAuthEmailIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-security-key.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-security-key.icon.ts index f6ac90cfd5d..573dc890428 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-security-key.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-security-key.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const TwoFactorAuthSecurityKeyIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts index 233533fc807..ff73bf2e255 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const TwoFactorAuthWebAuthnIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubico.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubico.icon.ts index 6bb989a9d15..8f6dca837ad 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubico.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubico.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const TwoFactorAuthYubicoIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/two-factor-timeout.icon.ts b/libs/auth/src/angular/icons/two-factor-timeout.icon.ts index 71d0aa549dc..3681670c124 100644 --- a/libs/auth/src/angular/icons/two-factor-timeout.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-timeout.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const TwoFactorTimeoutIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/user-lock.icon.ts b/libs/auth/src/angular/icons/user-lock.icon.ts index e85eac6fc2d..bc4fdd9e268 100644 --- a/libs/auth/src/angular/icons/user-lock.icon.ts +++ b/libs/auth/src/angular/icons/user-lock.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const UserLockIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/user-verification-biometrics-fingerprint.icon.ts b/libs/auth/src/angular/icons/user-verification-biometrics-fingerprint.icon.ts index f661f9330b1..e329d889574 100644 --- a/libs/auth/src/angular/icons/user-verification-biometrics-fingerprint.icon.ts +++ b/libs/auth/src/angular/icons/user-verification-biometrics-fingerprint.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const UserVerificationBiometricsIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/vault.icon.ts b/libs/auth/src/angular/icons/vault.icon.ts index e23944ab7db..a341bcd99f5 100644 --- a/libs/auth/src/angular/icons/vault.icon.ts +++ b/libs/auth/src/angular/icons/vault.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const VaultIcon = svgIcon` diff --git a/libs/auth/src/angular/icons/wave.icon.ts b/libs/auth/src/angular/icons/wave.icon.ts index 3e4483c1e05..5629c43fc8d 100644 --- a/libs/auth/src/angular/icons/wave.icon.ts +++ b/libs/auth/src/angular/icons/wave.icon.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { svgIcon } from "@bitwarden/components"; export const WaveIcon = svgIcon` diff --git a/libs/auth/src/angular/input-password/input-password.component.ts b/libs/auth/src/angular/input-password/input-password.component.ts index 135a6cfaaa8..8b0b6e4c159 100644 --- a/libs/auth/src/angular/input-password/input-password.component.ts +++ b/libs/auth/src/angular/input-password/input-password.component.ts @@ -17,6 +17,8 @@ import { HashPurpose } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/input-password/input-password.stories.ts b/libs/auth/src/angular/input-password/input-password.stories.ts index 84ba39e2916..708b74b9925 100644 --- a/libs/auth/src/angular/input-password/input-password.stories.ts +++ b/libs/auth/src/angular/input-password/input-password.stories.ts @@ -14,6 +14,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogService, ToastService } from "@bitwarden/components"; import { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management"; diff --git a/libs/auth/src/angular/login-approval/login-approval.component.spec.ts b/libs/auth/src/angular/login-approval/login-approval.component.spec.ts index 5fcc06c8476..afc2c67de12 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.spec.ts +++ b/libs/auth/src/angular/login-approval/login-approval.component.spec.ts @@ -14,6 +14,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { UserId } from "@bitwarden/common/types/guid"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogRef, DIALOG_DATA, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; diff --git a/libs/auth/src/angular/login-approval/login-approval.component.ts b/libs/auth/src/angular/login-approval/login-approval.component.ts index 43d9af64481..784bd93002e 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.ts +++ b/libs/auth/src/angular/login-approval/login-approval.component.ts @@ -17,6 +17,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DIALOG_DATA, DialogRef, diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts index 3ea9416b7e2..c49e54f8c19 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts @@ -26,6 +26,8 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { UserId } from "@bitwarden/common/types/guid"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts index d74deb443f5..f23d49f0015 100644 --- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts +++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts @@ -34,6 +34,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { UserId } from "@bitwarden/common/types/guid"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { ButtonModule, LinkModule, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; diff --git a/libs/auth/src/angular/login/login-secondary-content.component.ts b/libs/auth/src/angular/login/login-secondary-content.component.ts index b608542b375..fba5e70d93c 100644 --- a/libs/auth/src/angular/login/login-secondary-content.component.ts +++ b/libs/auth/src/angular/login/login-secondary-content.component.ts @@ -4,6 +4,8 @@ import { RouterModule } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { DefaultServerSettingsService } from "@bitwarden/common/platform/services/default-server-settings.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LinkModule } from "@bitwarden/components"; @Component({ diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index d5180f56785..8674453cf10 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -29,6 +29,8 @@ import { ValidationService } from "@bitwarden/common/platform/abstractions/valid import { Utils } from "@bitwarden/common/platform/misc/utils"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { UserId } from "@bitwarden/common/types/guid"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts b/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts index a2b0b23d05c..5d3ecc1751d 100644 --- a/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts +++ b/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts @@ -9,6 +9,8 @@ import { LoginSuccessHandlerService } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/password-callout/password-callout.component.ts b/libs/auth/src/angular/password-callout/password-callout.component.ts index 6968f384f07..03fba52336f 100644 --- a/libs/auth/src/angular/password-callout/password-callout.component.ts +++ b/libs/auth/src/angular/password-callout/password-callout.component.ts @@ -6,6 +6,8 @@ import { Component, Input } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CalloutModule } from "@bitwarden/components"; @Component({ diff --git a/libs/auth/src/angular/password-callout/password-callout.stories.ts b/libs/auth/src/angular/password-callout/password-callout.stories.ts index ce5b698b2e8..58862049e10 100644 --- a/libs/auth/src/angular/password-callout/password-callout.stories.ts +++ b/libs/auth/src/angular/password-callout/password-callout.stories.ts @@ -2,6 +2,8 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { I18nMockService } from "@bitwarden/components"; import { PasswordCalloutComponent } from "./password-callout.component"; diff --git a/libs/auth/src/angular/password-hint/password-hint.component.ts b/libs/auth/src/angular/password-hint/password-hint.component.ts index cf24c68e10d..99eb06293e0 100644 --- a/libs/auth/src/angular/password-hint/password-hint.component.ts +++ b/libs/auth/src/angular/password-hint/password-hint.component.ts @@ -13,6 +13,8 @@ import { PasswordHintRequest } from "@bitwarden/common/auth/models/request/passw import { ClientType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts b/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts index 86f08e79748..8aac5f73ffe 100644 --- a/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts +++ b/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts @@ -15,6 +15,8 @@ import { } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogService, FormFieldModule, SelectModule, ToastService } from "@bitwarden/components"; import { SelfHostedEnvConfigDialogComponent } from "../../self-hosted-env-config-dialog/self-hosted-env-config-dialog.component"; diff --git a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts index 981f7e554fb..353e3772c41 100644 --- a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts +++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts @@ -14,6 +14,8 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { ToastService } from "@bitwarden/components"; import { diff --git a/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts b/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts index ef032b72562..d4ecade52fe 100644 --- a/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts +++ b/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts @@ -6,6 +6,8 @@ import { ActivatedRoute, RouterModule } from "@angular/router"; import { Subject, firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { ButtonModule, IconModule } from "@bitwarden/components"; import { RegistrationExpiredLinkIcon } from "../../icons/registration-expired-link.icon"; diff --git a/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts b/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts index 4b13c6666e2..bc873593a83 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts @@ -6,6 +6,8 @@ import { ActivatedRoute, RouterModule } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LinkModule } from "@bitwarden/components"; /** diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.component.ts b/libs/auth/src/angular/registration/registration-start/registration-start.component.ts index 44d1d720a8d..ea756c967c6 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.component.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start.component.ts @@ -11,6 +11,8 @@ import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-a import { RegisterSendVerificationEmailRequest } from "@bitwarden/common/auth/models/request/registration/register-send-verification-email.request"; import { RegionConfig, Region } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts b/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts index 6047cc3d27a..e54e59a988a 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start.stories.ts @@ -15,6 +15,8 @@ import { Urls, } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts b/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts index 64478d63447..189c669423d 100644 --- a/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts +++ b/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts @@ -16,6 +16,8 @@ import { EnvironmentService, Region, } from "@bitwarden/common/platform/abstractions/environment.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogRef, AsyncActionsModule, diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts index 6001cb39085..95d54d589bc 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts @@ -1,6 +1,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { BehaviorSubject, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { FakeUserDecryptionOptions as UserDecryptionOptions, diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts index a4bc5f69b4c..ec274b9c4af 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { firstValueFrom } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserApiService, OrganizationUserResetPasswordEnrollmentRequest, diff --git a/libs/auth/src/angular/sso/sso.component.ts b/libs/auth/src/angular/sso/sso.component.ts index e0bdf8c26a1..f5a138af584 100644 --- a/libs/auth/src/angular/sso/sso.component.ts +++ b/libs/auth/src/angular/sso/sso.component.ts @@ -33,6 +33,8 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts index d8735d3fd54..cd4df5aee68 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts @@ -3,6 +3,8 @@ import { Component, Input, Output, EventEmitter } from "@angular/core"; import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogModule, ButtonModule, diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts index 96fee95940b..22d3906bb48 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts @@ -6,6 +6,8 @@ import { ReactiveFormsModule, FormsModule } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogModule, ButtonModule, diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts index 25235017bd1..2fff77b720e 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts @@ -12,6 +12,8 @@ import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.ser import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogModule, ButtonModule, diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts index 4efab4629c3..a8b435375db 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts @@ -13,6 +13,8 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogModule, ButtonModule, diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts index f334766bba9..abcbd557e0f 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts @@ -3,6 +3,8 @@ import { Component, Input } from "@angular/core"; import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogModule, ButtonModule, diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts index e52f08941d4..a2769e37c87 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts @@ -34,6 +34,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogService, ToastService } from "@bitwarden/components"; import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper-data.service"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index b14a368e066..57637fe9118 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -38,6 +38,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { UserId } from "@bitwarden/common/types/guid"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts index ce6cef0d670..fb0d7101cad 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts @@ -7,6 +7,8 @@ import { TwoFactorService, } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DialogRef, ButtonModule, diff --git a/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts b/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts index 58b1dca4cd1..8bb2f987d77 100644 --- a/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts +++ b/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts @@ -10,6 +10,8 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DIALOG_DATA, DialogRef, diff --git a/libs/auth/src/angular/user-verification/user-verification-dialog.types.ts b/libs/auth/src/angular/user-verification/user-verification-dialog.types.ts index cb03f4e18f7..93ae6d7d77f 100644 --- a/libs/auth/src/angular/user-verification/user-verification-dialog.types.ts +++ b/libs/auth/src/angular/user-verification/user-verification-dialog.types.ts @@ -1,4 +1,6 @@ import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { ButtonType } from "@bitwarden/components"; /** diff --git a/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts b/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts index ff4af51f732..7ea191ba2f9 100644 --- a/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts +++ b/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts @@ -19,6 +19,8 @@ import { UserVerificationOptions } from "@bitwarden/common/auth/types/user-verif import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { AsyncActionsModule, ButtonModule, diff --git a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts index 82bc53bb147..c9f83902fe2 100644 --- a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts +++ b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts @@ -30,6 +30,8 @@ import { VaultTimeoutSettingsService, } from "@bitwarden/common/key-management/vault-timeout"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { FormFieldModule, SelectModule } from "@bitwarden/components"; type VaultTimeoutForm = FormGroup<{ diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index e4453359015..49543e2f2ce 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionAccessDetailsResponse, CollectionDetailsResponse, diff --git a/libs/common/src/admin-console/models/response/organization-export.response.ts b/libs/common/src/admin-console/models/response/organization-export.response.ts index 6e42fe14c0d..19a8dd9ad94 100644 --- a/libs/common/src/admin-console/models/response/organization-export.response.ts +++ b/libs/common/src/admin-console/models/response/organization-export.response.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionResponse } from "@bitwarden/admin-console/common"; import { BaseResponse } from "../../../models/response/base.response"; diff --git a/libs/common/src/admin-console/models/response/provider/provider-user-bulk-public-key.response.ts b/libs/common/src/admin-console/models/response/provider/provider-user-bulk-public-key.response.ts index 1f497dd8c5f..7e6a2b2a505 100644 --- a/libs/common/src/admin-console/models/response/provider/provider-user-bulk-public-key.response.ts +++ b/libs/common/src/admin-console/models/response/provider/provider-user-bulk-public-key.response.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserBulkPublicKeyResponse } from "@bitwarden/admin-console/common"; export class ProviderUserBulkPublicKeyResponse extends OrganizationUserBulkPublicKeyResponse {} diff --git a/libs/common/src/auth/models/request/registration/register-finish.request.ts b/libs/common/src/auth/models/request/registration/register-finish.request.ts index 645bcf05b1b..bb577053c5c 100644 --- a/libs/common/src/auth/models/request/registration/register-finish.request.ts +++ b/libs/common/src/auth/models/request/registration/register-finish.request.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfType } from "@bitwarden/key-management"; import { KeysRequest } from "../../../../models/request/keys.request"; diff --git a/libs/common/src/auth/models/request/set-password.request.ts b/libs/common/src/auth/models/request/set-password.request.ts index 5aa74068591..7206cd98623 100644 --- a/libs/common/src/auth/models/request/set-password.request.ts +++ b/libs/common/src/auth/models/request/set-password.request.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfType } from "@bitwarden/key-management"; import { KeysRequest } from "../../../models/request/keys.request"; diff --git a/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts b/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts index 8c90fa379b4..8a107aa6c32 100644 --- a/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts +++ b/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserResetPasswordRequest } from "@bitwarden/admin-console/common"; export class UpdateTdeOffboardingPasswordRequest extends OrganizationUserResetPasswordRequest { diff --git a/libs/common/src/auth/models/request/update-temp-password.request.ts b/libs/common/src/auth/models/request/update-temp-password.request.ts index 8f922f9008b..39d6707e198 100644 --- a/libs/common/src/auth/models/request/update-temp-password.request.ts +++ b/libs/common/src/auth/models/request/update-temp-password.request.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserResetPasswordRequest } from "@bitwarden/admin-console/common"; export class UpdateTempPasswordRequest extends OrganizationUserResetPasswordRequest { diff --git a/libs/common/src/auth/models/response/identity-token.response.ts b/libs/common/src/auth/models/response/identity-token.response.ts index 5f128d340bf..3e2896eec64 100644 --- a/libs/common/src/auth/models/response/identity-token.response.ts +++ b/libs/common/src/auth/models/response/identity-token.response.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfType } from "@bitwarden/key-management"; import { BaseResponse } from "../../../models/response/base.response"; diff --git a/libs/common/src/auth/models/response/prelogin.response.ts b/libs/common/src/auth/models/response/prelogin.response.ts index b5ca78c3b79..e7c962242ce 100644 --- a/libs/common/src/auth/models/response/prelogin.response.ts +++ b/libs/common/src/auth/models/response/prelogin.response.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfType } from "@bitwarden/key-management"; import { BaseResponse } from "../../../models/response/base.response"; diff --git a/libs/common/src/auth/models/response/protected-device.response.ts b/libs/common/src/auth/models/response/protected-device.response.ts index 3cc3a3e0792..5695044c982 100644 --- a/libs/common/src/auth/models/response/protected-device.response.ts +++ b/libs/common/src/auth/models/response/protected-device.response.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { RotateableKeySet } from "@bitwarden/auth/common"; import { DeviceType } from "../../../enums"; diff --git a/libs/common/src/auth/services/auth.service.spec.ts b/libs/common/src/auth/services/auth.service.spec.ts index fc236c91a21..5dcb8c372e5 100644 --- a/libs/common/src/auth/services/auth.service.spec.ts +++ b/libs/common/src/auth/services/auth.service.spec.ts @@ -1,6 +1,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index da70baf3999..9700efe02ca 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -11,6 +11,8 @@ import { switchMap, } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { ApiService } from "../../abstractions/api.service"; diff --git a/libs/common/src/auth/services/master-password/master-password-api.service.spec.ts b/libs/common/src/auth/services/master-password/master-password-api.service.spec.ts index 64d4fdf1c7b..7a3b8762c13 100644 --- a/libs/common/src/auth/services/master-password/master-password-api.service.spec.ts +++ b/libs/common/src/auth/services/master-password/master-password-api.service.spec.ts @@ -2,6 +2,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfType } from "@bitwarden/key-management"; import { PasswordRequest } from "../../models/request/password.request"; diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts index 76c2d443d1d..d5b787d69f0 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts @@ -1,7 +1,11 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts index ef9c5ad3265..f491d7d5eb0 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts @@ -2,10 +2,14 @@ // @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { OrganizationUserApiService, OrganizationUserResetPasswordEnrollmentRequest, } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; diff --git a/libs/common/src/auth/services/token.service.spec.ts b/libs/common/src/auth/services/token.service.spec.ts index a56853c479c..e67e522368f 100644 --- a/libs/common/src/auth/services/token.service.spec.ts +++ b/libs/common/src/auth/services/token.service.spec.ts @@ -3,6 +3,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; import { FakeSingleUserStateProvider, FakeGlobalStateProvider } from "../../../spec"; diff --git a/libs/common/src/auth/services/token.service.ts b/libs/common/src/auth/services/token.service.ts index 61c00f69215..2c6883272c3 100644 --- a/libs/common/src/auth/services/token.service.ts +++ b/libs/common/src/auth/services/token.service.ts @@ -3,6 +3,8 @@ import { Observable, combineLatest, firstValueFrom, map } from "rxjs"; import { Opaque } from "type-fest"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason, decodeJwtTokenToJson } from "@bitwarden/auth/common"; import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts index d56dd6dda3b..33f276a87f2 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts @@ -1,12 +1,16 @@ import { mock } from "jest-mock-extended"; import { of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinLockType, PinServiceAbstraction, UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { BiometricsService, BiometricsStatus, diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.ts b/libs/common/src/auth/services/user-verification/user-verification.service.ts index cfa6800deed..5837042b93f 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.ts @@ -2,7 +2,11 @@ // @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { BiometricsService, BiometricsStatus, diff --git a/libs/common/src/auth/services/webauthn-login/webauthn-login.service.spec.ts b/libs/common/src/auth/services/webauthn-login/webauthn-login.service.spec.ts index 10444062349..56aa1139cda 100644 --- a/libs/common/src/auth/services/webauthn-login/webauthn-login.service.spec.ts +++ b/libs/common/src/auth/services/webauthn-login/webauthn-login.service.spec.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LoginStrategyServiceAbstraction, WebAuthnLoginCredentials } from "@bitwarden/auth/common"; import { LogService } from "../../../platform/abstractions/log.service"; diff --git a/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts b/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts index cea4bf29737..2d42329d27a 100644 --- a/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts +++ b/libs/common/src/auth/services/webauthn-login/webauthn-login.service.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LoginStrategyServiceAbstraction, WebAuthnLoginCredentials } from "@bitwarden/auth/common"; import { LogService } from "../../../platform/abstractions/log.service"; diff --git a/libs/common/src/auth/types/verification.ts b/libs/common/src/auth/types/verification.ts index 307a584fb36..4f45a6fdeed 100644 --- a/libs/common/src/auth/types/verification.ts +++ b/libs/common/src/auth/types/verification.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfConfig } from "@bitwarden/key-management"; import { MasterKey } from "../../types/key"; diff --git a/libs/common/src/billing/services/organization-billing.service.spec.ts b/libs/common/src/billing/services/organization-billing.service.spec.ts index 7b194dff637..43457f810d1 100644 --- a/libs/common/src/billing/services/organization-billing.service.spec.ts +++ b/libs/common/src/billing/services/organization-billing.service.spec.ts @@ -12,6 +12,8 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SyncService } from "@bitwarden/common/platform/sync"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; describe("BillingAccountProfileStateService", () => { diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts index fe5623fd5e6..b75134d28a8 100644 --- a/libs/common/src/billing/services/organization-billing.service.ts +++ b/libs/common/src/billing/services/organization-billing.service.ts @@ -5,6 +5,8 @@ import { Observable, of, switchMap } from "rxjs"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { ApiService } from "../../abstractions/api.service"; diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts index 5e89e0a5cb7..84ebf981f03 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts @@ -2,7 +2,11 @@ // @ts-strict-ignore import { firstValueFrom, map, Observable, Subject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { RotateableKeySet, UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { DeviceResponse } from "../../../auth/abstractions/devices/responses/device.response"; diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts index de9de5d781a..c1b291c086a 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts @@ -3,11 +3,15 @@ import { matches, mock } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { UserDecryptionOptionsServiceAbstraction, UserDecryptionOptions, } from "@bitwarden/auth/common"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { FakeAccountService, mockAccountServiceWith } from "../../../../spec/fake-account-service"; diff --git a/libs/common/src/key-management/key-connector/models/set-key-connector-key.request.ts b/libs/common/src/key-management/key-connector/models/set-key-connector-key.request.ts index 14132ab79f2..fec38b1d72d 100644 --- a/libs/common/src/key-management/key-connector/models/set-key-connector-key.request.ts +++ b/libs/common/src/key-management/key-connector/models/set-key-connector-key.request.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfConfig, KdfType } from "@bitwarden/key-management"; import { KeysRequest } from "../../../models/request/keys.request"; diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts index d5443cfc52c..6049d4db5a1 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts @@ -3,6 +3,8 @@ import { firstValueFrom, of, timeout, TimeoutError } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.ts index fc0b64f707b..905bc42defe 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.ts @@ -2,8 +2,12 @@ // @ts-strict-ignore import { combineLatest, filter, firstValueFrom, Observable, of, switchMap } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { Argon2KdfConfig, KdfConfig, diff --git a/libs/common/src/key-management/services/default-process-reload.service.ts b/libs/common/src/key-management/services/default-process-reload.service.ts index 860dac54855..e43fa5f0977 100644 --- a/libs/common/src/key-management/services/default-process-reload.service.ts +++ b/libs/common/src/key-management/services/default-process-reload.service.ts @@ -2,7 +2,11 @@ // @ts-strict-ignore import { firstValueFrom, map, timeout } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { BiometricStateService } from "@bitwarden/key-management"; import { AccountService } from "../../auth/abstractions/account.service"; diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.spec.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.spec.ts index b5e9544b01b..349aa474872 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.spec.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.spec.ts @@ -3,11 +3,15 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom, map, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction, FakeUserDecryptionOptions as UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { BiometricStateService, KeyService } from "@bitwarden/key-management"; import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts index 07b8a8c297d..16e38ae0b52 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts @@ -14,10 +14,14 @@ import { tap, } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { BiometricStateService, KeyService } from "@bitwarden/key-management"; import { PolicyService } from "../../../admin-console/abstractions/policy/policy.service.abstraction"; diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts index 5fdae07b2d7..b17e85ca9c4 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.spec.ts @@ -3,8 +3,14 @@ import { MockProxy, any, mock } from "jest-mock-extended"; import { BehaviorSubject, from, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { BiometricsService } from "@bitwarden/key-management"; import { FakeAccountService, mockAccountServiceWith } from "../../../../spec"; diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts index d71b8972727..131f826fd33 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts @@ -2,8 +2,14 @@ // @ts-strict-ignore import { combineLatest, concatMap, filter, firstValueFrom, map, timeout } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { BiometricsService } from "@bitwarden/key-management"; import { SearchService } from "../../../abstractions/search.service"; diff --git a/libs/common/src/models/export/collection-with-id.export.ts b/libs/common/src/models/export/collection-with-id.export.ts index ef850bc6039..a93f07b54e5 100644 --- a/libs/common/src/models/export/collection-with-id.export.ts +++ b/libs/common/src/models/export/collection-with-id.export.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { Collection as CollectionDomain, CollectionView } from "@bitwarden/admin-console/common"; import { CollectionExport } from "./collection.export"; diff --git a/libs/common/src/models/export/collection.export.ts b/libs/common/src/models/export/collection.export.ts index 89137c34875..3f913a8c3e3 100644 --- a/libs/common/src/models/export/collection.export.ts +++ b/libs/common/src/models/export/collection.export.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { Collection as CollectionDomain, CollectionView } from "@bitwarden/admin-console/common"; import { EncString } from "../../platform/models/domain/enc-string"; diff --git a/libs/common/src/models/request/import-organization-ciphers.request.ts b/libs/common/src/models/request/import-organization-ciphers.request.ts index 759b69b21e3..69f23f64b64 100644 --- a/libs/common/src/models/request/import-organization-ciphers.request.ts +++ b/libs/common/src/models/request/import-organization-ciphers.request.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionWithIdRequest } from "@bitwarden/admin-console/common"; import { CipherRequest } from "../../vault/models/request/cipher.request"; diff --git a/libs/common/src/models/request/kdf.request.ts b/libs/common/src/models/request/kdf.request.ts index f0bd376317f..7ffdbcb4a4b 100644 --- a/libs/common/src/models/request/kdf.request.ts +++ b/libs/common/src/models/request/kdf.request.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfType } from "@bitwarden/key-management"; import { PasswordRequest } from "../../auth/models/request/password.request"; diff --git a/libs/common/src/platform/abstractions/key-generation.service.ts b/libs/common/src/platform/abstractions/key-generation.service.ts index 8314efe3469..91c630ed638 100644 --- a/libs/common/src/platform/abstractions/key-generation.service.ts +++ b/libs/common/src/platform/abstractions/key-generation.service.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfConfig } from "@bitwarden/key-management"; import { CsprngArray } from "../../types/csprng"; diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index f9c5ab4a843..a1e914da531 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -8,6 +8,8 @@ import { Observable, of, switchMap } from "rxjs"; import { getHostname, parse } from "tldts"; import { Merge } from "type-fest"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/common/src/platform/models/domain/enc-string.spec.ts b/libs/common/src/platform/models/domain/enc-string.spec.ts index 1ab61745eb3..f565174ba9c 100644 --- a/libs/common/src/platform/models/domain/enc-string.spec.ts +++ b/libs/common/src/platform/models/domain/enc-string.spec.ts @@ -1,5 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { makeEncString, makeStaticByteArray } from "../../../../spec"; diff --git a/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts b/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts index bf834e8dd93..9dca079bdba 100644 --- a/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts +++ b/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, bufferCount, firstValueFrom, ObservedValueOf, Subject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; import { awaitAsync } from "../../../../spec"; diff --git a/libs/common/src/platform/notifications/internal/default-notifications.service.ts b/libs/common/src/platform/notifications/internal/default-notifications.service.ts index 40c93f8f22a..4cbc8227364 100644 --- a/libs/common/src/platform/notifications/internal/default-notifications.service.ts +++ b/libs/common/src/platform/notifications/internal/default-notifications.service.ts @@ -11,6 +11,8 @@ import { switchMap, } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; import { AccountService } from "../../../auth/abstractions/account.service"; diff --git a/libs/common/src/platform/services/container.service.ts b/libs/common/src/platform/services/container.service.ts index 1428b2bbd7c..501c8ace92c 100644 --- a/libs/common/src/platform/services/container.service.ts +++ b/libs/common/src/platform/services/container.service.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/common/src/platform/services/key-generation.service.spec.ts b/libs/common/src/platform/services/key-generation.service.spec.ts index 0a9e997b428..4fdad48e0fa 100644 --- a/libs/common/src/platform/services/key-generation.service.spec.ts +++ b/libs/common/src/platform/services/key-generation.service.spec.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PBKDF2KdfConfig, Argon2KdfConfig } from "@bitwarden/key-management"; import { CryptoFunctionService } from "../../key-management/crypto/abstractions/crypto-function.service"; diff --git a/libs/common/src/platform/services/key-generation.service.ts b/libs/common/src/platform/services/key-generation.service.ts index dcd1f4f95d7..49f99eb79a9 100644 --- a/libs/common/src/platform/services/key-generation.service.ts +++ b/libs/common/src/platform/services/key-generation.service.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfConfig, PBKDF2KdfConfig, Argon2KdfConfig, KdfType } from "@bitwarden/key-management"; import { CryptoFunctionService } from "../../key-management/crypto/abstractions/crypto-function.service"; diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts index a66b2a9cb6f..6531be58f05 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KdfConfigService, KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management"; import { BitwardenClient } from "@bitwarden/sdk-internal"; diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 5c381c7dd1b..7027b5134a0 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -14,6 +14,8 @@ import { throwIfEmpty, } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService, KdfConfigService, KdfConfig, KdfType } from "@bitwarden/key-management"; import { BitwardenClient, diff --git a/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts b/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts index 84511d1e71a..b9f189c5f06 100644 --- a/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts +++ b/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { DefaultKeyService } from "@bitwarden/key-management"; import { CsprngArray } from "../../types/csprng"; diff --git a/libs/common/src/platform/services/user-auto-unlock-key.service.ts b/libs/common/src/platform/services/user-auto-unlock-key.service.ts index bf64c13b060..33537cd0e2d 100644 --- a/libs/common/src/platform/services/user-auto-unlock-key.service.ts +++ b/libs/common/src/platform/services/user-auto-unlock-key.service.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { UserId } from "../../types/guid"; diff --git a/libs/common/src/platform/sync/core-sync.service.ts b/libs/common/src/platform/sync/core-sync.service.ts index 4020c75f764..63f9ab17fb3 100644 --- a/libs/common/src/platform/sync/core-sync.service.ts +++ b/libs/common/src/platform/sync/core-sync.service.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { firstValueFrom, map, Observable, of, switchMap } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; import { ApiService } from "../../abstractions/api.service"; diff --git a/libs/common/src/platform/sync/default-sync.service.spec.ts b/libs/common/src/platform/sync/default-sync.service.spec.ts index ded06c8be6b..29543672dbe 100644 --- a/libs/common/src/platform/sync/default-sync.service.spec.ts +++ b/libs/common/src/platform/sync/default-sync.service.spec.ts @@ -1,12 +1,18 @@ import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason, UserDecryptionOptions, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { Matrix } from "../../../spec/matrix"; diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index e9f6c60af64..6e1a8f28443 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -2,11 +2,15 @@ // @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionData, CollectionDetailsResponse, CollectionService, } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; // FIXME: remove `src` and fix import diff --git a/libs/common/src/platform/sync/sync.response.ts b/libs/common/src/platform/sync/sync.response.ts index bc94eff67a4..5055652a0d1 100644 --- a/libs/common/src/platform/sync/sync.response.ts +++ b/libs/common/src/platform/sync/sync.response.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionDetailsResponse } from "@bitwarden/admin-console/common"; import { PolicyResponse } from "../../admin-console/models/response/policy.response"; diff --git a/libs/common/src/services/api.service.spec.ts b/libs/common/src/services/api.service.spec.ts index eca6066b9b7..fffe0478254 100644 --- a/libs/common/src/services/api.service.spec.ts +++ b/libs/common/src/services/api.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; import { TokenService } from "../auth/abstractions/token.service"; diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 639daa7c658..95aea41e68b 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -2,12 +2,16 @@ // @ts-strict-ignore import { firstValueFrom } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionAccessDetailsResponse, CollectionDetailsResponse, CollectionRequest, CollectionResponse, } from "@bitwarden/admin-console/common"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { LogoutReason } from "@bitwarden/auth/common"; import { ApiService as ApiServiceAbstraction } from "../abstractions/api.service"; diff --git a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts index 66edc5a4838..255fd9b4aff 100644 --- a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts +++ b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts @@ -1,6 +1,8 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, Subject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts index c91181a004a..c2e9b305618 100644 --- a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts +++ b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.ts @@ -12,6 +12,8 @@ import { takeWhile, } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/common/src/tools/send/models/domain/send.spec.ts b/libs/common/src/tools/send/models/domain/send.spec.ts index 7112ad7f751..8df9a144108 100644 --- a/libs/common/src/tools/send/models/domain/send.spec.ts +++ b/libs/common/src/tools/send/models/domain/send.spec.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { makeStaticByteArray, mockEnc } from "../../../../../spec"; diff --git a/libs/common/src/tools/send/services/send.service.abstraction.ts b/libs/common/src/tools/send/services/send.service.abstraction.ts index 921f0565624..0cf951e4197 100644 --- a/libs/common/src/tools/send/services/send.service.abstraction.ts +++ b/libs/common/src/tools/send/services/send.service.abstraction.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Observable } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { UserKeyRotationDataProvider } from "@bitwarden/key-management"; import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer"; diff --git a/libs/common/src/tools/send/services/send.service.spec.ts b/libs/common/src/tools/send/services/send.service.spec.ts index 611cc9c7b76..777bc54f299 100644 --- a/libs/common/src/tools/send/services/send.service.spec.ts +++ b/libs/common/src/tools/send/services/send.service.spec.ts @@ -1,6 +1,8 @@ import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { diff --git a/libs/common/src/tools/send/services/send.service.ts b/libs/common/src/tools/send/services/send.service.ts index 3a5bcbe997b..2556fa2e908 100644 --- a/libs/common/src/tools/send/services/send.service.ts +++ b/libs/common/src/tools/send/services/send.service.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Observable, concatMap, distinctUntilChanged, firstValueFrom, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PBKDF2KdfConfig, KeyService } from "@bitwarden/key-management"; import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index a67dfcef8b9..fc809058161 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Observable } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { UserKeyRotationDataProvider } from "@bitwarden/key-management"; import { UriMatchStrategySetting } from "../../models/domain/domain-service"; diff --git a/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts b/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts index b7241e3ae37..7324fe22c8d 100644 --- a/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Observable } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { UserKeyRotationDataProvider } from "@bitwarden/key-management"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; diff --git a/libs/common/src/vault/models/domain/attachment.spec.ts b/libs/common/src/vault/models/domain/attachment.spec.ts index eab67320679..f03ce927082 100644 --- a/libs/common/src/vault/models/domain/attachment.spec.ts +++ b/libs/common/src/vault/models/domain/attachment.spec.ts @@ -1,5 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec"; diff --git a/libs/common/src/vault/models/domain/cipher.spec.ts b/libs/common/src/vault/models/domain/cipher.spec.ts index a889f0b969c..5c98ceda9f7 100644 --- a/libs/common/src/vault/models/domain/cipher.spec.ts +++ b/libs/common/src/vault/models/domain/cipher.spec.ts @@ -2,6 +2,8 @@ import { mock } from "jest-mock-extended"; import { Jsonify } from "type-fest"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { CipherType as SdkCipherType, diff --git a/libs/common/src/vault/services/cipher-authorization.service.spec.ts b/libs/common/src/vault/services/cipher-authorization.service.spec.ts index 33af28842ca..01574d04df5 100644 --- a/libs/common/src/vault/services/cipher-authorization.service.spec.ts +++ b/libs/common/src/vault/services/cipher-authorization.service.spec.ts @@ -1,6 +1,8 @@ import { mock } from "jest-mock-extended"; import { Observable, firstValueFrom, of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; diff --git a/libs/common/src/vault/services/cipher-authorization.service.ts b/libs/common/src/vault/services/cipher-authorization.service.ts index b415760a035..f30e80cdfb8 100644 --- a/libs/common/src/vault/services/cipher-authorization.service.ts +++ b/libs/common/src/vault/services/cipher-authorization.service.ts @@ -1,5 +1,7 @@ import { combineLatest, map, Observable, of, shareReplay, switchMap } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index b15bc4a9112..9e56bac2ca0 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -2,6 +2,8 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, map, of } from "rxjs"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CipherDecryptionKeys, KeyService } from "@bitwarden/key-management"; import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 6bea56baa5e..2693d9d4644 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -4,6 +4,8 @@ import { combineLatest, filter, firstValueFrom, map, Observable, Subject, switch import { SemVer } from "semver"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { ApiService } from "../../abstractions/api.service"; diff --git a/libs/common/src/vault/services/folder/folder.service.spec.ts b/libs/common/src/vault/services/folder/folder.service.spec.ts index 0c61efc288a..38dea851456 100644 --- a/libs/common/src/vault/services/folder/folder.service.spec.ts +++ b/libs/common/src/vault/services/folder/folder.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { makeEncString } from "../../../../spec"; diff --git a/libs/common/src/vault/services/folder/folder.service.ts b/libs/common/src/vault/services/folder/folder.service.ts index ba87f1c3148..12d02958049 100644 --- a/libs/common/src/vault/services/folder/folder.service.ts +++ b/libs/common/src/vault/services/folder/folder.service.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Observable, Subject, firstValueFrom, map, shareReplay, switchMap, merge } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/importer/src/components/import.component.ts b/libs/importer/src/components/import.component.ts index 2fee88852b5..7b8f49b796b 100644 --- a/libs/importer/src/components/import.component.ts +++ b/libs/importer/src/components/import.component.ts @@ -18,6 +18,8 @@ import * as JSZip from "jszip"; import { Observable, Subject, lastValueFrom, combineLatest, firstValueFrom } from "rxjs"; import { combineLatestWith, filter, map, switchMap, takeUntil } from "rxjs/operators"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { safeProvider, SafeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; diff --git a/libs/importer/src/importers/base-importer.ts b/libs/importer/src/importers/base-importer.ts index 0594b6014e8..9033997a475 100644 --- a/libs/importer/src/importers/base-importer.ts +++ b/libs/importer/src/importers/base-importer.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import * as papa from "papaparse"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; diff --git a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts index af29d8263c6..4291f7b1ab2 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; diff --git a/libs/importer/src/importers/padlock-csv-importer.ts b/libs/importer/src/importers/padlock-csv-importer.ts index f4943e5979b..86b569fbc52 100644 --- a/libs/importer/src/importers/padlock-csv-importer.ts +++ b/libs/importer/src/importers/padlock-csv-importer.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { ImportResult } from "../models/import-result"; diff --git a/libs/importer/src/importers/passpack-csv-importer.ts b/libs/importer/src/importers/passpack-csv-importer.ts index a426d6db416..17b0c148896 100644 --- a/libs/importer/src/importers/passpack-csv-importer.ts +++ b/libs/importer/src/importers/passpack-csv-importer.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { ImportResult } from "../models/import-result"; diff --git a/libs/importer/src/models/import-result.ts b/libs/importer/src/models/import-result.ts index 9fddbb420a1..9d94b410e7b 100644 --- a/libs/importer/src/models/import-result.ts +++ b/libs/importer/src/models/import-result.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; diff --git a/libs/importer/src/services/import-collection.service.abstraction.ts b/libs/importer/src/services/import-collection.service.abstraction.ts index 539232ef094..48e6e7a4a8c 100644 --- a/libs/importer/src/services/import-collection.service.abstraction.ts +++ b/libs/importer/src/services/import-collection.service.abstraction.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; export abstract class ImportCollectionServiceAbstraction { diff --git a/libs/importer/src/services/import.service.abstraction.ts b/libs/importer/src/services/import.service.abstraction.ts index 5f7a31bcc14..d869dc71cc7 100644 --- a/libs/importer/src/services/import.service.abstraction.ts +++ b/libs/importer/src/services/import.service.abstraction.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; diff --git a/libs/importer/src/services/import.service.spec.ts b/libs/importer/src/services/import.service.spec.ts index 908f062ecc1..30309a3d9c2 100644 --- a/libs/importer/src/services/import.service.spec.ts +++ b/libs/importer/src/services/import.service.spec.ts @@ -1,6 +1,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; diff --git a/libs/importer/src/services/import.service.ts b/libs/importer/src/services/import.service.ts index bd18e78d542..adfa427c660 100644 --- a/libs/importer/src/services/import.service.ts +++ b/libs/importer/src/services/import.service.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionWithIdRequest, diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index f1f1286dfc5..6d2e8fd20ec 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -1,6 +1,8 @@ import { mock } from "jest-mock-extended"; import { BehaviorSubject, bufferCount, firstValueFrom, lastValueFrom, of, take } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index 9372dafd3ea..1d4fcc86a0c 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -10,6 +10,8 @@ import { switchMap, } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data"; import { BaseEncryptedOrganizationKey } from "@bitwarden/common/admin-console/models/domain/encrypted-organization-key"; diff --git a/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts b/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts index 639cf562caa..71f12340ebc 100644 --- a/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts +++ b/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts @@ -1,3 +1,5 @@ +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts index c5256c841d9..3d68b7124c1 100644 --- a/libs/vault/src/cipher-form/cipher-form.stories.ts +++ b/libs/vault/src/cipher-form/cipher-form.stories.ts @@ -12,6 +12,8 @@ import { } from "@storybook/angular"; import { BehaviorSubject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { NudgeStatus, NudgesService } from "@bitwarden/angular/vault"; diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts index 074b4b9e4b4..1e9916e76a4 100644 --- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.spec.ts @@ -5,6 +5,8 @@ import { By } from "@angular/platform-browser"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts index 82615368b91..a1cf33c449b 100644 --- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts @@ -6,6 +6,8 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms"; import { concatMap, map } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; diff --git a/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts index 8e1dde22324..a91a84e91c1 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts @@ -3,6 +3,8 @@ import { inject, Injectable } from "@angular/core"; import { combineLatest, filter, firstValueFrom, map, switchMap } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index cf78e78a65f..02968d6d149 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -2,6 +2,8 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnChanges, OnDestroy } from "@angular/core"; import { firstValueFrom, Observable, Subject, takeUntil } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { diff --git a/libs/vault/src/cipher-view/item-details/item-details-v2.component.spec.ts b/libs/vault/src/cipher-view/item-details/item-details-v2.component.spec.ts index da3d790368c..f093cd020b5 100644 --- a/libs/vault/src/cipher-view/item-details/item-details-v2.component.spec.ts +++ b/libs/vault/src/cipher-view/item-details/item-details-v2.component.spec.ts @@ -1,6 +1,8 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts b/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts index 1335df74bb9..fbedcbb54df 100644 --- a/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts +++ b/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts @@ -3,6 +3,8 @@ import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; diff --git a/libs/vault/src/components/assign-collections.component.ts b/libs/vault/src/components/assign-collections.component.ts index db62f096faa..faa2dae072a 100644 --- a/libs/vault/src/components/assign-collections.component.ts +++ b/libs/vault/src/components/assign-collections.component.ts @@ -24,6 +24,8 @@ import { tap, } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { From 0e0be0a3decf2ae93976ebf6257086a09de718c2 Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Fri, 23 May 2025 09:07:32 -0400 Subject: [PATCH 146/245] ignore one eslint error (#14896) --- libs/vault/src/components/assign-collections.component.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/vault/src/components/assign-collections.component.spec.ts b/libs/vault/src/components/assign-collections.component.spec.ts index d6707cd1064..800bf5393ad 100644 --- a/libs/vault/src/components/assign-collections.component.spec.ts +++ b/libs/vault/src/components/assign-collections.component.spec.ts @@ -3,6 +3,8 @@ import { By } from "@angular/platform-browser"; import { mock } from "jest-mock-extended"; import { of } from "rxjs"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; From c6af80f3eb812006e56c57be7f1c3c290a06ce1b Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Fri, 23 May 2025 11:48:23 -0400 Subject: [PATCH 147/245] PM-21651 [For Automation Purposes] add test IDs to notification bar (#14863) * PM-21651 [For Automation Purposes] Please add IDs to some of the main components * option items * dynamic test id * mitigate feedback * clean up logic --- .../buttons/option-selection-button.ts | 3 ++ .../option-selection-button.lit-stories.ts | 2 + .../confirmation/container.lit-stories.ts | 8 +++- .../notification/container.lit-stories.ts | 5 ++- .../option-selection.lit-stories.ts | 5 ++- .../notification/confirmation/container.ts | 4 +- .../components/notification/container.ts | 4 +- .../option-selection/option-item.ts | 3 ++ .../option-selection/option-items.ts | 3 ++ .../option-selection/option-selection.ts | 5 +++ .../content/components/rows/button-row.ts | 1 + apps/browser/src/autofill/notification/bar.ts | 42 +++++++++++++++---- 12 files changed, 70 insertions(+), 15 deletions(-) diff --git a/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts b/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts index 3912c791d34..ddefd02f6e1 100644 --- a/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts @@ -10,6 +10,7 @@ import { AngleUp, AngleDown } from "../icons"; export type OptionSelectionButtonProps = { disabled: boolean; icon?: Option["icon"]; + id: string; text?: string; theme: Theme; toggledOn: boolean; @@ -19,6 +20,7 @@ export type OptionSelectionButtonProps = { export function OptionSelectionButton({ disabled, icon, + id, text, theme, toggledOn, @@ -31,6 +33,7 @@ export function OptionSelectionButton({ return html` diff --git a/apps/browser/src/auth/popup/account-switching/account.component.html b/apps/browser/src/auth/popup/account-switching/account.component.html index d2e15d31899..d22ce9c9366 100644 --- a/apps/browser/src/auth/popup/account-switching/account.component.html +++ b/apps/browser/src/auth/popup/account-switching/account.component.html @@ -32,13 +32,13 @@
- + From 04ed114e0ef2845527535778284142daa7ef35ed Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 26 May 2025 00:30:52 +0200 Subject: [PATCH 152/245] [BEEEP/PM-8492] Add autostart for flatpak (#12016) * Add autostart for flatpak via ashpd * Fix clippy errors * Cargo fmt * Fix clippy --- apps/desktop/desktop_native/Cargo.lock | 1 + apps/desktop/desktop_native/Cargo.toml | 1 + apps/desktop/desktop_native/core/Cargo.toml | 1 + .../core/src/autostart/linux.rs | 21 ++++++++++ .../desktop_native/core/src/autostart/mod.rs | 5 +++ .../core/src/autostart/unimplemented.rs | 5 +++ apps/desktop/desktop_native/core/src/lib.rs | 1 + apps/desktop/desktop_native/napi/index.d.ts | 3 ++ apps/desktop/desktop_native/napi/src/lib.rs | 10 +++++ apps/desktop/src/main/messaging.main.ts | 39 ++++++++++++------- 10 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 apps/desktop/desktop_native/core/src/autostart/linux.rs create mode 100644 apps/desktop/desktop_native/core/src/autostart/mod.rs create mode 100644 apps/desktop/desktop_native/core/src/autostart/unimplemented.rs diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index e5d90446ddc..34819a3981b 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -863,6 +863,7 @@ dependencies = [ "anyhow", "arboard", "argon2", + "ashpd", "base64", "bitwarden-russh", "byteorder", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 71da53c867d..c4a2ed98e70 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -13,6 +13,7 @@ aes = "=0.8.4" anyhow = "=1.0.94" arboard = { version = "=3.5.0", default-features = false } argon2 = "=0.5.3" +ashpd = "=0.11.0" base64 = "=0.22.1" bindgen = "=0.71.1" bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "3d48f140fd506412d186203238993163a8c4e536" } diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index b71081aaa1f..7cd67dbad6a 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -85,6 +85,7 @@ desktop_objc = { path = "../objc" } [target.'cfg(target_os = "linux")'.dependencies] oo7 = { workspace = true } libc = { workspace = true } +ashpd = { workspace = true } zbus = { workspace = true, optional = true } zbus_polkit = { workspace = true, optional = true } diff --git a/apps/desktop/desktop_native/core/src/autostart/linux.rs b/apps/desktop/desktop_native/core/src/autostart/linux.rs new file mode 100644 index 00000000000..1fd02a6ea5d --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autostart/linux.rs @@ -0,0 +1,21 @@ +use anyhow::Result; +use ashpd::desktop::background::Background; + +pub async fn set_autostart(autostart: bool, params: Vec) -> Result<()> { + let request = if params.is_empty() { + Background::request().auto_start(autostart) + } else { + Background::request().command(params).auto_start(autostart) + }; + + match request.send().await.and_then(|r| r.response()) { + Ok(response) => { + println!("[ASHPD] Autostart enabled: {:?}", response); + Ok(()) + } + Err(err) => { + println!("[ASHPD] Error enabling autostart: {}", err); + Err(anyhow::anyhow!("error enabling autostart {}", err)) + } + } +} diff --git a/apps/desktop/desktop_native/core/src/autostart/mod.rs b/apps/desktop/desktop_native/core/src/autostart/mod.rs new file mode 100644 index 00000000000..78e27eb433e --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autostart/mod.rs @@ -0,0 +1,5 @@ +#[cfg_attr(target_os = "linux", path = "linux.rs")] +#[cfg_attr(target_os = "windows", path = "unimplemented.rs")] +#[cfg_attr(target_os = "macos", path = "unimplemented.rs")] +mod autostart_impl; +pub use autostart_impl::*; diff --git a/apps/desktop/desktop_native/core/src/autostart/unimplemented.rs b/apps/desktop/desktop_native/core/src/autostart/unimplemented.rs new file mode 100644 index 00000000000..14f567bdc65 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autostart/unimplemented.rs @@ -0,0 +1,5 @@ +use anyhow::Result; + +pub async fn set_autostart(_autostart: bool, _params: Vec) -> Result<()> { + unimplemented!(); +} diff --git a/apps/desktop/desktop_native/core/src/lib.rs b/apps/desktop/desktop_native/core/src/lib.rs index 0a16ee65be3..a72ec04e9c2 100644 --- a/apps/desktop/desktop_native/core/src/lib.rs +++ b/apps/desktop/desktop_native/core/src/lib.rs @@ -1,4 +1,5 @@ pub mod autofill; +pub mod autostart; pub mod biometric; pub mod clipboard; pub mod crypto; diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index 952f2571c5d..b3c6f715e98 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -111,6 +111,9 @@ export declare namespace ipc { send(message: string): number } } +export declare namespace autostart { + export function setAutostart(autostart: boolean, params: Array): Promise +} export declare namespace autofill { export function runCommand(value: string): Promise export const enum UserVerification { diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 37796ef6f59..079872a3b03 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -477,6 +477,16 @@ pub mod ipc { } } +#[napi] +pub mod autostart { + #[napi] + pub async fn set_autostart(autostart: bool, params: Vec) -> napi::Result<()> { + desktop_core::autostart::set_autostart(autostart, params) + .await + .map_err(|e| napi::Error::from_reason(format!("Error setting autostart - {e} - {e:?}"))) + } +} + #[napi] pub mod autofill { use desktop_core::ipc::server::{Message, MessageType}; diff --git a/apps/desktop/src/main/messaging.main.ts b/apps/desktop/src/main/messaging.main.ts index bb4063d64fd..556fa293108 100644 --- a/apps/desktop/src/main/messaging.main.ts +++ b/apps/desktop/src/main/messaging.main.ts @@ -6,8 +6,11 @@ import * as path from "path"; import { app, ipcMain } from "electron"; import { firstValueFrom } from "rxjs"; +import { autostart } from "@bitwarden/desktop-napi"; + import { Main } from "../main"; import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; +import { isFlatpak } from "../utils"; import { MenuUpdateRequest } from "./menu/menu.updater"; @@ -122,20 +125,24 @@ export class MessagingMain { private addOpenAtLogin() { if (process.platform === "linux") { - const data = `[Desktop Entry] -Type=Application -Version=${app.getVersion()} -Name=Bitwarden -Comment=Bitwarden startup script -Exec=${app.getPath("exe")} -StartupNotify=false -Terminal=false`; + if (isFlatpak()) { + autostart.setAutostart(true, []).catch((e) => {}); + } else { + const data = `[Desktop Entry] + Type=Application + Version=${app.getVersion()} + Name=Bitwarden + Comment=Bitwarden startup script + Exec=${app.getPath("exe")} + StartupNotify=false + Terminal=false`; - const dir = path.dirname(this.linuxStartupFile()); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); + const dir = path.dirname(this.linuxStartupFile()); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } + fs.writeFileSync(this.linuxStartupFile(), data); } - fs.writeFileSync(this.linuxStartupFile(), data); } else { app.setLoginItemSettings({ openAtLogin: true }); } @@ -143,8 +150,12 @@ Terminal=false`; private removeOpenAtLogin() { if (process.platform === "linux") { - if (fs.existsSync(this.linuxStartupFile())) { - fs.unlinkSync(this.linuxStartupFile()); + if (isFlatpak()) { + autostart.setAutostart(false, []).catch((e) => {}); + } else { + if (fs.existsSync(this.linuxStartupFile())) { + fs.unlinkSync(this.linuxStartupFile()); + } } } else { app.setLoginItemSettings({ openAtLogin: false }); From 56b565a47ebaf6fc49a021a86127f42d554aa7d3 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Mon, 26 May 2025 03:21:30 -0400 Subject: [PATCH 153/245] Add lowdb to ignored dependencies (#14907) * Added lowdb to ignored dependencies * Changed to allowedVersions instead of ignoring for more context. --- .github/renovate.json5 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 0ebc2c210a2..1b84ccdab01 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -405,6 +405,12 @@ commitMessagePrefix: "[deps] KM:", reviewers: ["team:team-key-management-dev"], }, + { + // Any versions of lowdb above 1.0.0 are not compatible with CommonJS. + matchPackageNames: ["lowdb"], + allowedVersions: "1.0.0", + description: "Higher versions of lowdb are not compatible with CommonJS", + }, ], ignoreDeps: ["@types/koa-bodyparser", "bootstrap", "node-ipc", "@bitwarden/sdk-internal"], } From 745ab219467fbe31ce0e05308ff3232c0067e1e1 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 26 May 2025 14:38:02 +0200 Subject: [PATCH 154/245] Remove unused imports in browser and desktop (#14875) Removes unused imports from browser and desktop. These were missed in #14795. --- .../autofill/popup/fido2/fido2-cipher-row.component.ts | 4 ---- .../autofill/popup/settings/notifications.component.ts | 2 -- .../src/platform/popup/layout/popup-layout.stories.ts | 3 --- .../popup/generator/credential-generator.component.ts | 2 -- .../send-v2/send-created/send-created.component.ts | 3 +-- .../open-attachments/open-attachments.component.ts | 10 ++-------- .../autofill-vault-list-items.component.ts | 9 +-------- .../vault-list-items-container.component.ts | 1 - .../popup/settings/download-bitwarden.component.ts | 2 -- .../trash-list-items-container.component.ts | 1 - .../popup/settings/vault-settings-v2.component.ts | 2 -- .../src/platform/components/approve-ssh-request.ts | 2 -- 12 files changed, 4 insertions(+), 37 deletions(-) diff --git a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts index 98e73d2174c..02df3ffe9b3 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts @@ -10,8 +10,6 @@ import { ButtonModule, IconButtonModule, ItemModule, - SectionComponent, - SectionHeaderComponent, TypographyModule, } from "@bitwarden/components"; @@ -27,8 +25,6 @@ import { IconButtonModule, ItemModule, JslibModule, - SectionComponent, - SectionHeaderComponent, TypographyModule, ], }) diff --git a/apps/browser/src/autofill/popup/settings/notifications.component.ts b/apps/browser/src/autofill/popup/settings/notifications.component.ts index be447e3f885..476b601c4e5 100644 --- a/apps/browser/src/autofill/popup/settings/notifications.component.ts +++ b/apps/browser/src/autofill/popup/settings/notifications.component.ts @@ -18,7 +18,6 @@ import { } from "@bitwarden/components"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; -import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @@ -31,7 +30,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co RouterModule, PopupPageComponent, PopupHeaderComponent, - PopupFooterComponent, PopOutComponent, ItemModule, CardComponent, diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts index f20049f6cde..48940f5fa10 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts +++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts @@ -185,7 +185,6 @@ class MockVaultPageComponent {} PopupPageComponent, PopupHeaderComponent, MockAddButtonComponent, - MockPopoutButtonComponent, MockCurrentAccountComponent, VaultComponent, ], @@ -290,9 +289,7 @@ class MockSettingsPageComponent {} PopupHeaderComponent, PopupFooterComponent, ButtonModule, - MockAddButtonComponent, MockPopoutButtonComponent, - MockCurrentAccountComponent, VaultComponent, IconButtonModule, ], diff --git a/apps/browser/src/tools/popup/generator/credential-generator.component.ts b/apps/browser/src/tools/popup/generator/credential-generator.component.ts index 9c1af07efdd..a2ef4be6620 100644 --- a/apps/browser/src/tools/popup/generator/credential-generator.component.ts +++ b/apps/browser/src/tools/popup/generator/credential-generator.component.ts @@ -7,7 +7,6 @@ import { GeneratorModule } from "@bitwarden/generator-components"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; -import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @@ -22,7 +21,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co PopOutComponent, PopupHeaderComponent, PopupPageComponent, - PopupFooterComponent, RouterModule, ItemModule, ], diff --git a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts index 7191040ac6f..7680e05dd5b 100644 --- a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts @@ -3,7 +3,7 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { ActivatedRoute, Router, RouterLink, RouterModule } from "@angular/router"; +import { ActivatedRoute, Router, RouterModule } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -31,7 +31,6 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page PopOutComponent, PopupHeaderComponent, PopupPageComponent, - RouterLink, RouterModule, PopupFooterComponent, IconModule, diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts index 9189ea51313..d44b54bcb96 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts @@ -18,13 +18,7 @@ import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { - BadgeModule, - CardComponent, - ItemModule, - ToastService, - TypographyModule, -} from "@bitwarden/components"; +import { BadgeModule, ItemModule, ToastService, TypographyModule } from "@bitwarden/components"; import BrowserPopupUtils from "../../../../../../platform/popup/browser-popup-utils"; import { FilePopoutUtilsService } from "../../../../../../tools/popup/services/file-popout-utils.service"; @@ -33,7 +27,7 @@ import { FilePopoutUtilsService } from "../../../../../../tools/popup/services/f standalone: true, selector: "app-open-attachments", templateUrl: "./open-attachments.component.html", - imports: [BadgeModule, CommonModule, ItemModule, JslibModule, TypographyModule, CardComponent], + imports: [BadgeModule, CommonModule, ItemModule, JslibModule, TypographyModule], }) export class OpenAttachmentsComponent implements OnInit { /** Cipher `id` */ diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts index 72d51776f7b..bdc0d7ae5bc 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts @@ -6,12 +6,7 @@ import { combineLatest, map, Observable } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherType } from "@bitwarden/common/vault/enums"; -import { - IconButtonModule, - SectionComponent, - SectionHeaderComponent, - TypographyModule, -} from "@bitwarden/components"; +import { IconButtonModule, TypographyModule } from "@bitwarden/components"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; @@ -23,11 +18,9 @@ import { VaultListItemsContainerComponent } from "../vault-list-items-container/ standalone: true, imports: [ CommonModule, - SectionComponent, TypographyModule, VaultListItemsContainerComponent, JslibModule, - SectionHeaderComponent, IconButtonModule, ], selector: "app-autofill-vault-list-items", diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts index 6df1bdf8ae5..073d49333b5 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts @@ -74,7 +74,6 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options ScrollingModule, DisclosureComponent, DisclosureTriggerForDirective, - DecryptionFailureDialogComponent, ], selector: "app-vault-list-items-container", templateUrl: "vault-list-items-container.component.html", diff --git a/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts b/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts index fa7efa87bda..0287b7d504f 100644 --- a/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts +++ b/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts @@ -9,7 +9,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CardComponent, LinkModule, TypographyModule } from "@bitwarden/components"; -import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @@ -26,7 +25,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co PopOutComponent, CardComponent, TypographyModule, - CurrentAccountComponent, LinkModule, ], }) diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts index cbfc89bf922..b4899e12eda 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts @@ -49,7 +49,6 @@ import { PopupCipherView } from "../../views/popup-cipher.view"; IconButtonModule, OrgIconDirective, TypographyModule, - DecryptionFailureDialogComponent, ], changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts index c969f0436df..049e853d2cd 100644 --- a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts @@ -10,7 +10,6 @@ import { ItemModule, ToastOptions, ToastService } from "@bitwarden/components"; import { BrowserApi } from "../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; -import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @@ -22,7 +21,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co JslibModule, RouterModule, PopupPageComponent, - PopupFooterComponent, PopupHeaderComponent, PopOutComponent, ItemModule, diff --git a/apps/desktop/src/platform/components/approve-ssh-request.ts b/apps/desktop/src/platform/components/approve-ssh-request.ts index c6c7388ecf1..515fd94ecd6 100644 --- a/apps/desktop/src/platform/components/approve-ssh-request.ts +++ b/apps/desktop/src/platform/components/approve-ssh-request.ts @@ -13,7 +13,6 @@ import { IconButtonModule, DialogService, } from "@bitwarden/components"; -import { CipherFormGeneratorComponent } from "@bitwarden/vault"; export interface ApproveSshRequestParams { cipherName: string; @@ -30,7 +29,6 @@ export interface ApproveSshRequestParams { DialogModule, CommonModule, JslibModule, - CipherFormGeneratorComponent, ButtonModule, IconButtonModule, ReactiveFormsModule, From 43c963032db7f8a92fc97e24f7044af22f5751cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 16:25:30 +0200 Subject: [PATCH 155/245] [deps] Architecture: Update lint-staged to v16 (#14925) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- package-lock.json | 160 ++++++---------------------------------------- package.json | 2 +- 2 files changed, 22 insertions(+), 140 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2e2623f89cc..88302755623 100644 --- a/package-lock.json +++ b/package-lock.json @@ -153,7 +153,7 @@ "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", "json5": "2.2.3", - "lint-staged": "15.5.1", + "lint-staged": "16.0.0", "mini-css-extract-plugin": "2.9.2", "nx": "20.8.0", "postcss": "8.5.3", @@ -25970,28 +25970,28 @@ } }, "node_modules/lint-staged": { - "version": "15.5.1", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.1.tgz", - "integrity": "sha512-6m7u8mue4Xn6wK6gZvSCQwBvMBR36xfY24nF5bMTf2MHDYG6S3yhJuOgdYVw99hsjyDt2d4z168b3naI8+NWtQ==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.0.0.tgz", + "integrity": "sha512-sUCprePs6/rbx4vKC60Hez6X10HPkpDJaGcy3D1NdwR7g1RcNkWL8q9mJMreOqmHBTs+1sNFp+wOiX9fr+hoOQ==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^5.4.1", "commander": "^13.1.0", "debug": "^4.4.0", - "execa": "^8.0.1", "lilconfig": "^3.1.3", - "listr2": "^8.2.5", + "listr2": "^8.3.3", "micromatch": "^4.0.8", + "nano-spawn": "^1.0.0", "pidtree": "^0.6.0", "string-argv": "^0.3.2", - "yaml": "^2.7.0" + "yaml": "^2.7.1" }, "bin": { "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": ">=18.12.0" + "node": ">=20.18" }, "funding": { "url": "https://opencollective.com/lint-staged" @@ -26077,53 +26077,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lint-staged/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, "node_modules/lint-staged/node_modules/is-fullwidth-code-point": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", @@ -26137,19 +26090,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lint-staged/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lint-staged/node_modules/listr2": { "version": "8.3.3", "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", @@ -26168,64 +26108,6 @@ "node": ">=18.0.0" } }, - "node_modules/lint-staged/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lint-staged/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lint-staged/node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -26277,19 +26159,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/lint-staged/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lint-staged/node_modules/wrap-ansi": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", @@ -28632,6 +28501,19 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nano-spawn": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-1.0.2.tgz", + "integrity": "sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", diff --git a/package.json b/package.json index ce6adf3009d..8a8e80bd632 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", "json5": "2.2.3", - "lint-staged": "15.5.1", + "lint-staged": "16.0.0", "mini-css-extract-plugin": "2.9.2", "nx": "20.8.0", "postcss": "8.5.3", From beb00a206b7b48f4f053b394b06160b5dd2c3f2c Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 26 May 2025 17:02:28 +0200 Subject: [PATCH 156/245] Add UUID helpers to the SDK (#14939) * Add UUID helpers to the SDK * Address review feedback --- .../platform/abstractions/sdk/sdk.service.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/libs/common/src/platform/abstractions/sdk/sdk.service.ts b/libs/common/src/platform/abstractions/sdk/sdk.service.ts index 3adf3291bbf..07dfb2aa0df 100644 --- a/libs/common/src/platform/abstractions/sdk/sdk.service.ts +++ b/libs/common/src/platform/abstractions/sdk/sdk.service.ts @@ -1,9 +1,10 @@ import { Observable } from "rxjs"; -import { BitwardenClient } from "@bitwarden/sdk-internal"; +import { BitwardenClient, Uuid } from "@bitwarden/sdk-internal"; import { UserId } from "../../../types/guid"; import { Rc } from "../../misc/reference-counting/rc"; +import { Utils } from "../../misc/utils"; export class UserNotLoggedInError extends Error { constructor(userId: UserId) { @@ -11,6 +12,30 @@ export class UserNotLoggedInError extends Error { } } +export class InvalidUuid extends Error { + constructor(uuid: string) { + super(`Invalid UUID: ${uuid}`); + } +} + +/** + * Converts a string to UUID. Will throw an error if the UUID is non valid. + */ +export function asUuid(uuid: string): T { + if (Utils.isGuid(uuid)) { + return uuid as T; + } + + throw new InvalidUuid(uuid); +} + +/** + * Converts a UUID to the string representation. + */ +export function uuidToString(uuid: T): string { + return uuid as unknown as string; +} + export abstract class SdkService { /** * Retrieve the version of the SDK. From a6e2012087f1dc8e49c4b2d70a2f04a6a8ddd37c Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 27 May 2025 10:03:54 +0200 Subject: [PATCH 157/245] [PM-21600] Migrate account and security to standalone (#14762) Migrates account and security settings components to standalone and removing them from the `LooseComponentsModule`. --- .../settings/account/account.component.ts | 15 ++++++-- .../account/change-avatar-dialog.component.ts | 7 +++- .../account/change-email.component.spec.ts | 3 +- .../account/change-email.component.ts | 5 ++- .../settings/account/danger-zone.component.ts | 4 +-- .../account/deauthorize-sessions.component.ts | 7 ++-- .../delete-account-dialog.component.ts | 6 +++- .../settings/account/profile.component.ts | 7 +++- .../account/selectable-avatar.component.ts | 6 +++- .../settings/security/api-key.component.ts | 7 ++-- .../security/security-keys.component.ts | 7 ++-- .../settings/security/security.component.ts | 7 ++-- .../src/app/shared/loose-components.module.ts | 35 ------------------- 13 files changed, 62 insertions(+), 54 deletions(-) diff --git a/apps/web/src/app/auth/settings/account/account.component.ts b/apps/web/src/app/auth/settings/account/account.component.ts index cfc01f17674..c06df56e386 100644 --- a/apps/web/src/app/auth/settings/account/account.component.ts +++ b/apps/web/src/app/auth/settings/account/account.component.ts @@ -8,16 +8,27 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { DialogService } from "@bitwarden/components"; +import { HeaderModule } from "../../../layouts/header/header.module"; +import { SharedModule } from "../../../shared"; import { PurgeVaultComponent } from "../../../vault/settings/purge-vault.component"; +import { ChangeEmailComponent } from "./change-email.component"; +import { DangerZoneComponent } from "./danger-zone.component"; import { DeauthorizeSessionsComponent } from "./deauthorize-sessions.component"; import { DeleteAccountDialogComponent } from "./delete-account-dialog.component"; +import { ProfileComponent } from "./profile.component"; import { SetAccountVerifyDevicesDialogComponent } from "./set-account-verify-devices-dialog.component"; @Component({ - selector: "app-account", templateUrl: "account.component.html", - standalone: false, + standalone: true, + imports: [ + SharedModule, + HeaderModule, + ProfileComponent, + ChangeEmailComponent, + DangerZoneComponent, + ], }) export class AccountComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts index 5d71333c0de..80fdb20954f 100644 --- a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts @@ -24,6 +24,10 @@ import { ToastService, } from "@bitwarden/components"; +import { SharedModule } from "../../../shared"; + +import { SelectableAvatarComponent } from "./selectable-avatar.component"; + type ChangeAvatarDialogData = { profile: ProfileResponse; }; @@ -31,7 +35,8 @@ type ChangeAvatarDialogData = { @Component({ templateUrl: "change-avatar-dialog.component.html", encapsulation: ViewEncapsulation.None, - standalone: false, + standalone: true, + imports: [SharedModule, SelectableAvatarComponent], }) export class ChangeAvatarDialogComponent implements OnInit, OnDestroy { profile: ProfileResponse; diff --git a/apps/web/src/app/auth/settings/account/change-email.component.spec.ts b/apps/web/src/app/auth/settings/account/change-email.component.spec.ts index 838a50b5c2e..f5c0733e5b0 100644 --- a/apps/web/src/app/auth/settings/account/change-email.component.spec.ts +++ b/apps/web/src/app/auth/settings/account/change-email.component.spec.ts @@ -33,8 +33,7 @@ describe("ChangeEmailComponent", () => { accountService = mockAccountServiceWith("UserId" as UserId); await TestBed.configureTestingModule({ - declarations: [ChangeEmailComponent], - imports: [ReactiveFormsModule, SharedModule], + imports: [ReactiveFormsModule, SharedModule, ChangeEmailComponent], providers: [ { provide: AccountService, useValue: accountService }, { provide: ApiService, useValue: apiService }, diff --git a/apps/web/src/app/auth/settings/account/change-email.component.ts b/apps/web/src/app/auth/settings/account/change-email.component.ts index c86c8c2f4f7..98f704d6044 100644 --- a/apps/web/src/app/auth/settings/account/change-email.component.ts +++ b/apps/web/src/app/auth/settings/account/change-email.component.ts @@ -14,10 +14,13 @@ import { UserId } from "@bitwarden/common/types/guid"; import { ToastService } from "@bitwarden/components"; import { KdfConfigService, KeyService } from "@bitwarden/key-management"; +import { SharedModule } from "../../../shared"; + @Component({ selector: "app-change-email", templateUrl: "change-email.component.html", - standalone: false, + standalone: true, + imports: [SharedModule], }) export class ChangeEmailComponent implements OnInit { tokenSent = false; diff --git a/apps/web/src/app/auth/settings/account/danger-zone.component.ts b/apps/web/src/app/auth/settings/account/danger-zone.component.ts index 91f22c7d08f..e07b6e6b8db 100644 --- a/apps/web/src/app/auth/settings/account/danger-zone.component.ts +++ b/apps/web/src/app/auth/settings/account/danger-zone.component.ts @@ -3,8 +3,8 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; -import { JslibModule } from "@bitwarden/angular/jslib.module"; import { TypographyModule } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; /** * Component for the Danger Zone section of the Account/Organization Settings page. @@ -13,6 +13,6 @@ import { TypographyModule } from "@bitwarden/components"; selector: "app-danger-zone", templateUrl: "danger-zone.component.html", standalone: true, - imports: [TypographyModule, JslibModule, CommonModule], + imports: [CommonModule, TypographyModule, I18nPipe], }) export class DangerZoneComponent {} diff --git a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts index da4d2dce9d7..a48e968ab3e 100644 --- a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts +++ b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts @@ -1,6 +1,7 @@ import { Component } from "@angular/core"; import { FormBuilder } from "@angular/forms"; +import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { Verification } from "@bitwarden/common/auth/types/verification"; @@ -9,10 +10,12 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { SharedModule } from "../../../shared"; + @Component({ - selector: "app-deauthorize-sessions", templateUrl: "deauthorize-sessions.component.html", - standalone: false, + standalone: true, + imports: [SharedModule, UserVerificationFormInputComponent], }) export class DeauthorizeSessionsComponent { deauthForm = this.formBuilder.group({ diff --git a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts index 8a3575af5ba..0fc2276b779 100644 --- a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts @@ -3,15 +3,19 @@ import { Component } from "@angular/core"; import { FormBuilder } from "@angular/forms"; +import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular"; import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; import { Verification } from "@bitwarden/common/auth/types/verification"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogRef, DialogService, ToastService } from "@bitwarden/components"; +import { SharedModule } from "../../../shared"; + @Component({ templateUrl: "delete-account-dialog.component.html", - standalone: false, + standalone: true, + imports: [SharedModule, UserVerificationFormInputComponent], }) export class DeleteAccountDialogComponent { deleteForm = this.formBuilder.group({ diff --git a/apps/web/src/app/auth/settings/account/profile.component.ts b/apps/web/src/app/auth/settings/account/profile.component.ts index dc3997f58bb..a33efd742aa 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.ts +++ b/apps/web/src/app/auth/settings/account/profile.component.ts @@ -14,12 +14,17 @@ import { ProfileResponse } from "@bitwarden/common/models/response/profile.respo import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { DynamicAvatarComponent } from "../../../components/dynamic-avatar.component"; +import { SharedModule } from "../../../shared"; +import { AccountFingerprintComponent } from "../../../shared/components/account-fingerprint/account-fingerprint.component"; + import { ChangeAvatarDialogComponent } from "./change-avatar-dialog.component"; @Component({ selector: "app-profile", templateUrl: "profile.component.html", - standalone: false, + standalone: true, + imports: [SharedModule, DynamicAvatarComponent, AccountFingerprintComponent], }) export class ProfileComponent implements OnInit, OnDestroy { loading = true; diff --git a/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts index 33c307882c5..a53e3990090 100644 --- a/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts +++ b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts @@ -1,7 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { NgClass } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { AvatarModule } from "@bitwarden/components"; + @Component({ selector: "selectable-avatar", template: ` `, - standalone: false, + standalone: true, + imports: [NgClass, AvatarModule], }) export class SelectableAvatarComponent { @Input() id: string; diff --git a/apps/web/src/app/auth/settings/security/api-key.component.ts b/apps/web/src/app/auth/settings/security/api-key.component.ts index 4f87c082881..5e61b4b4584 100644 --- a/apps/web/src/app/auth/settings/security/api-key.component.ts +++ b/apps/web/src/app/auth/settings/security/api-key.component.ts @@ -3,12 +3,15 @@ import { Component, Inject } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; +import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { ApiKeyResponse } from "@bitwarden/common/auth/models/response/api-key.response"; import { Verification } from "@bitwarden/common/auth/types/verification"; import { DIALOG_DATA, DialogConfig, DialogService } from "@bitwarden/components"; +import { SharedModule } from "../../../shared"; + export type ApiKeyDialogData = { keyType: string; isRotation?: boolean; @@ -21,9 +24,9 @@ export type ApiKeyDialogData = { apiKeyDescription: string; }; @Component({ - selector: "app-api-key", templateUrl: "api-key.component.html", - standalone: false, + standalone: true, + imports: [SharedModule, UserVerificationFormInputComponent], }) export class ApiKeyComponent { clientId: string; diff --git a/apps/web/src/app/auth/settings/security/security-keys.component.ts b/apps/web/src/app/auth/settings/security/security-keys.component.ts index 98e743f57dc..6d33193cdde 100644 --- a/apps/web/src/app/auth/settings/security/security-keys.component.ts +++ b/apps/web/src/app/auth/settings/security/security-keys.component.ts @@ -8,12 +8,15 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { DialogService } from "@bitwarden/components"; +import { SharedModule } from "../../../shared"; + import { ApiKeyComponent } from "./api-key.component"; +import { ChangeKdfModule } from "./change-kdf/change-kdf.module"; @Component({ - selector: "app-security-keys", templateUrl: "security-keys.component.html", - standalone: false, + standalone: true, + imports: [SharedModule, ChangeKdfModule], }) export class SecurityKeysComponent implements OnInit { showChangeKdf = true; diff --git a/apps/web/src/app/auth/settings/security/security.component.ts b/apps/web/src/app/auth/settings/security/security.component.ts index 41b1af17abb..95733d693e2 100644 --- a/apps/web/src/app/auth/settings/security/security.component.ts +++ b/apps/web/src/app/auth/settings/security/security.component.ts @@ -4,10 +4,13 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { HeaderModule } from "../../../layouts/header/header.module"; +import { SharedModule } from "../../../shared"; + @Component({ - selector: "app-security", templateUrl: "security.component.html", - standalone: false, + standalone: true, + imports: [SharedModule, HeaderModule], }) export class SecurityComponent implements OnInit { showChangePassword = true; diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index e59633ee499..44323614f17 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -15,23 +15,12 @@ import { AcceptFamilySponsorshipComponent } from "../admin-console/organizations import { RecoverDeleteComponent } from "../auth/recover-delete.component"; import { RecoverTwoFactorComponent } from "../auth/recover-two-factor.component"; import { SetPasswordComponent } from "../auth/set-password.component"; -import { AccountComponent } from "../auth/settings/account/account.component"; -import { ChangeAvatarDialogComponent } from "../auth/settings/account/change-avatar-dialog.component"; -import { ChangeEmailComponent } from "../auth/settings/account/change-email.component"; import { DangerZoneComponent } from "../auth/settings/account/danger-zone.component"; -import { DeauthorizeSessionsComponent } from "../auth/settings/account/deauthorize-sessions.component"; -import { DeleteAccountDialogComponent } from "../auth/settings/account/delete-account-dialog.component"; -import { ProfileComponent } from "../auth/settings/account/profile.component"; -import { SelectableAvatarComponent } from "../auth/settings/account/selectable-avatar.component"; import { EmergencyAccessConfirmComponent } from "../auth/settings/emergency-access/confirm/emergency-access-confirm.component"; import { EmergencyAccessAddEditComponent } from "../auth/settings/emergency-access/emergency-access-add-edit.component"; import { EmergencyAccessComponent } from "../auth/settings/emergency-access/emergency-access.component"; import { EmergencyAccessTakeoverComponent } from "../auth/settings/emergency-access/takeover/emergency-access-takeover.component"; import { EmergencyAccessViewComponent } from "../auth/settings/emergency-access/view/emergency-access-view.component"; -import { ApiKeyComponent } from "../auth/settings/security/api-key.component"; -import { ChangeKdfModule } from "../auth/settings/security/change-kdf/change-kdf.module"; -import { SecurityKeysComponent } from "../auth/settings/security/security-keys.component"; -import { SecurityComponent } from "../auth/settings/security/security.component"; import { UserVerificationModule } from "../auth/shared/components/user-verification"; import { UpdatePasswordComponent } from "../auth/update-password.component"; import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component"; @@ -39,7 +28,6 @@ import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component" import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.component"; import { SponsoredFamiliesComponent } from "../billing/settings/sponsored-families.component"; import { SponsoringOrgRowComponent } from "../billing/settings/sponsoring-org-row.component"; -import { DynamicAvatarComponent } from "../components/dynamic-avatar.component"; // eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "../dirt/reports/pages/organizations/exposed-passwords-report.component"; // eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module @@ -68,8 +56,6 @@ import { SharedModule } from "./shared.module"; imports: [ SharedModule, UserVerificationModule, - ChangeKdfModule, - DynamicAvatarComponent, AccountFingerprintComponent, OrganizationBadgeModule, PipesModule, @@ -85,11 +71,6 @@ import { SharedModule } from "./shared.module"; ], declarations: [ AcceptFamilySponsorshipComponent, - AccountComponent, - ApiKeyComponent, - ChangeEmailComponent, - DeauthorizeSessionsComponent, - DeleteAccountDialogComponent, EmergencyAccessAddEditComponent, EmergencyAccessComponent, EmergencyAccessConfirmComponent, @@ -104,15 +85,10 @@ import { SharedModule } from "./shared.module"; OrgUserConfirmComponent, OrgWeakPasswordsReportComponent, PremiumBadgeComponent, - ProfileComponent, - ChangeAvatarDialogComponent, PurgeVaultComponent, RecoverDeleteComponent, RecoverTwoFactorComponent, RemovePasswordComponent, - SecurityComponent, - SecurityKeysComponent, - SelectableAvatarComponent, SetPasswordComponent, SponsoredFamiliesComponent, FreeBitwardenFamiliesComponent, @@ -125,12 +101,6 @@ import { SharedModule } from "./shared.module"; exports: [ UserVerificationModule, PremiumBadgeComponent, - AccountComponent, - ApiKeyComponent, - ChangeEmailComponent, - DeauthorizeSessionsComponent, - DeleteAccountDialogComponent, - DynamicAvatarComponent, EmergencyAccessAddEditComponent, EmergencyAccessComponent, EmergencyAccessConfirmComponent, @@ -146,15 +116,10 @@ import { SharedModule } from "./shared.module"; OrgUserConfirmComponent, OrgWeakPasswordsReportComponent, PremiumBadgeComponent, - ProfileComponent, - ChangeAvatarDialogComponent, PurgeVaultComponent, RecoverDeleteComponent, RecoverTwoFactorComponent, RemovePasswordComponent, - SecurityComponent, - SecurityKeysComponent, - SelectableAvatarComponent, SetPasswordComponent, SponsoredFamiliesComponent, FreeBitwardenFamiliesComponent, From 45f2104fd8905ff28fe4c6fe570cdf59ae9cb1d3 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 27 May 2025 14:31:27 +0200 Subject: [PATCH 158/245] fix: broken SDK interface (#14959) --- .../src/platform/services/sdk/default-sdk.service.ts | 12 +++++++++++- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 7027b5134a0..8e84642fb99 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -152,7 +152,15 @@ export class DefaultSdkService implements SdkService { const settings = this.toSettings(env); const client = await this.sdkClientFactory.createSdkClient(settings); - await this.initializeClient(client, account, kdfParams, privateKey, userKey, orgKeys); + await this.initializeClient( + userId, + client, + account, + kdfParams, + privateKey, + userKey, + orgKeys, + ); return client; }; @@ -182,6 +190,7 @@ export class DefaultSdkService implements SdkService { } private async initializeClient( + userId: UserId, client: BitwardenClient, account: AccountInfo, kdfParams: KdfConfig, @@ -190,6 +199,7 @@ export class DefaultSdkService implements SdkService { orgKeys: Record | null, ) { await client.crypto().initialize_user_crypto({ + userId, email: account.email, method: { decryptedKey: { decrypted_user_key: userKey.keyB64 } }, kdfParams: diff --git a/package-lock.json b/package-lock.json index 88302755623..ff21acbc20b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@angular/platform-browser": "18.2.13", "@angular/platform-browser-dynamic": "18.2.13", "@angular/router": "18.2.13", - "@bitwarden/sdk-internal": "0.2.0-main.168", + "@bitwarden/sdk-internal": "0.2.0-main.177", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "3.1.0", @@ -4803,9 +4803,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.2.0-main.168", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.168.tgz", - "integrity": "sha512-NU10oqw+GI9oHrh8/i/IC8/7oaYmswqC2E/0Zc56xC3jY7uNgFZgpae7JhyMU6UxzrAjiEqdmGnm+AGWFiPG8w==", + "version": "0.2.0-main.177", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.177.tgz", + "integrity": "sha512-2fp/g0WJDPPrIqrU88QrwoJsZTzoi7S7eCf+Qq0/8x3ImqQyoYJEdHdz06YHjUdS0CzucPrwTo5zJ/ZvcLNOmQ==", "license": "GPL-3.0" }, "node_modules/@bitwarden/send-ui": { diff --git a/package.json b/package.json index 8a8e80bd632..d27d919fb16 100644 --- a/package.json +++ b/package.json @@ -159,7 +159,7 @@ "@angular/platform-browser": "18.2.13", "@angular/platform-browser-dynamic": "18.2.13", "@angular/router": "18.2.13", - "@bitwarden/sdk-internal": "0.2.0-main.168", + "@bitwarden/sdk-internal": "0.2.0-main.177", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "3.1.0", From 888e2031a702f3e12c61b666aa87fa8fc62d3cd4 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 27 May 2025 08:24:53 -0500 Subject: [PATCH 159/245] [PM-21090] Vault - Repeated Syncs (#14740) * move `fullSync` contents to private methods in prep to storing the respective promise * store in-flight sync so multiple calls to the sync service are avoided * Revert "store in-flight sync so multiple calls to the sync service are avoided" This reverts commit 233c8e9d4b92d448f63e157b01c168d840c7e531. * Revert "move `fullSync` contents to private methods in prep to storing the respective promise" This reverts commit 3f686ac6a4b41e332180478f269d9fde85f562a1. * store inflight API calls for sync service - This avoids duplicate network requests in a relatively short amount of time but still allows consumers to call `fullSync` if needed * add debug log for duplicate sync --- .../sync/default-sync.service.spec.ts | 65 +++++++++++++++---- .../src/platform/sync/default-sync.service.ts | 32 ++++++++- 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/libs/common/src/platform/sync/default-sync.service.spec.ts b/libs/common/src/platform/sync/default-sync.service.spec.ts index 29543672dbe..fc6b9481bd5 100644 --- a/libs/common/src/platform/sync/default-sync.service.spec.ts +++ b/libs/common/src/platform/sync/default-sync.service.spec.ts @@ -130,23 +130,23 @@ describe("DefaultSyncService", () => { const user1 = "user1" as UserId; + const emptySyncResponse = new SyncResponse({ + profile: { + id: user1, + }, + folders: [], + collections: [], + ciphers: [], + sends: [], + domains: [], + policies: [], + }); + describe("fullSync", () => { beforeEach(() => { accountService.activeAccount$ = of({ id: user1 } as Account); Matrix.autoMockMethod(authService.authStatusFor$, () => of(AuthenticationStatus.Unlocked)); - apiService.getSync.mockResolvedValue( - new SyncResponse({ - profile: { - id: user1, - }, - folders: [], - collections: [], - ciphers: [], - sends: [], - domains: [], - policies: [], - }), - ); + apiService.getSync.mockResolvedValue(emptySyncResponse); Matrix.autoMockMethod(userDecryptionOptionsService.userDecryptionOptionsById$, () => of({ hasMasterPassword: true } satisfies UserDecryptionOptions), ); @@ -201,5 +201,44 @@ describe("DefaultSyncService", () => { expect(apiService.refreshIdentityToken).toHaveBeenCalledTimes(1); expect(apiService.getSync).toHaveBeenCalledTimes(1); }); + + describe("in-flight syncs", () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it("does not call getSync when one is already in progress", async () => { + const fullSyncPromises = [sut.fullSync(true), sut.fullSync(false), sut.fullSync(false)]; + + jest.advanceTimersByTime(100); + + await Promise.all(fullSyncPromises); + + expect(apiService.getSync).toHaveBeenCalledTimes(1); + }); + + it("does not call refreshIdentityToken when one is already in progress", async () => { + const fullSyncPromises = [sut.fullSync(true), sut.fullSync(false), sut.fullSync(false)]; + + jest.advanceTimersByTime(100); + + await Promise.all(fullSyncPromises); + + expect(apiService.refreshIdentityToken).toHaveBeenCalledTimes(1); + }); + + it("resets the in-flight properties when the complete", async () => { + const fullSyncPromises = [sut.fullSync(true), sut.fullSync(true)]; + + await Promise.all(fullSyncPromises); + + expect(sut["inFlightApiCalls"].refreshToken).toBeNull(); + expect(sut["inFlightApiCalls"].sync).toBeNull(); + }); + }); }); }); diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index 6e1a8f28443..47ac3784c33 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -58,11 +58,21 @@ import { MessageSender } from "../messaging"; import { StateProvider } from "../state"; import { CoreSyncService } from "./core-sync.service"; +import { SyncResponse } from "./sync.response"; import { SyncOptions } from "./sync.service"; export class DefaultSyncService extends CoreSyncService { syncInProgress = false; + /** The promises associated with any in-flight api calls. */ + private inFlightApiCalls: { + refreshToken: Promise | null; + sync: Promise | null; + } = { + refreshToken: null, + sync: null, + }; + constructor( private masterPasswordService: InternalMasterPasswordServiceAbstraction, accountService: AccountService, @@ -141,9 +151,24 @@ export class DefaultSyncService extends CoreSyncService { try { if (!skipTokenRefresh) { - await this.apiService.refreshIdentityToken(); + // Store the promise so multiple calls to refresh the token are not made + if (this.inFlightApiCalls.refreshToken === null) { + this.inFlightApiCalls.refreshToken = this.apiService.refreshIdentityToken(); + } + + await this.inFlightApiCalls.refreshToken; } - const response = await this.apiService.getSync(); + + // Store the promise so multiple calls to sync are not made + if (this.inFlightApiCalls.sync === null) { + this.inFlightApiCalls.sync = this.apiService.getSync(); + } else { + this.logService.debug( + "Sync: Sync network call already in progress, returning existing promise", + ); + } + + const response = await this.inFlightApiCalls.sync; await this.syncProfile(response.profile); await this.syncFolders(response.folders, response.profile.id); @@ -162,6 +187,9 @@ export class DefaultSyncService extends CoreSyncService { } else { return this.syncCompleted(false, userId); } + } finally { + this.inFlightApiCalls.refreshToken = null; + this.inFlightApiCalls.sync = null; } } From f4f659c52a0703d9f1e868185925185799f34b5e Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 27 May 2025 15:38:33 +0200 Subject: [PATCH 160/245] Increase allowed memory for chromatic (#14964) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d27d919fb16..f63db41e1f3 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,8 @@ "lint:dep-ownership": "tsc --project ./scripts/tsconfig.json && node ./scripts/dist/dep-ownership.js", "docs:json": "compodoc -p ./tsconfig.json -e json -d . --disableRoutesGraph", "storybook": "ng run components:storybook", - "build-storybook": "ng run components:build-storybook", - "build-storybook:ci": "ng run components:build-storybook --webpack-stats-json", + "build-storybook": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" ng run components:build-storybook", + "build-storybook:ci": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" ng run components:build-storybook --webpack-stats-json", "test-stories": "test-storybook --url http://localhost:6006", "test-stories:watch": "test-stories --watch", "postinstall": "patch-package" From 97a591e738b3d062cdf8b75efd8b1ccef8934547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Tue, 27 May 2025 09:51:14 -0400 Subject: [PATCH 161/245] [PM-16793] port credential generator service to providers (#14071) * introduce extension service * deprecate legacy forwarder types * eliminate repeat algorithm emissions * extend logging to preference management * align forwarder ids with vendor ids * fix duplicate policy emissions; debugging required logger enhancements ----- Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- apps/browser/src/_locales/en/messages.json | 3 + apps/desktop/src/locales/en/messages.json | 39 +- .../policies/password-generator.component.ts | 26 +- apps/web/src/locales/en/messages.json | 27 +- .../tools/extension/extension.service.spec.ts | 1 + libs/common/src/tools/log/disabled-logger.ts | 24 + .../src/tools/log/disabled-semantic-logger.ts | 22 - libs/common/src/tools/log/factory.ts | 79 +- libs/common/src/tools/log/index.ts | 2 + libs/common/src/tools/log/types.ts | 11 + libs/common/src/tools/log/util.ts | 12 + libs/common/src/tools/private-classifier.ts | 2 +- libs/common/src/tools/public-classifier.ts | 2 +- libs/common/src/tools/rx.rxjs.ts | 13 + libs/common/src/tools/rx.spec.ts | 1504 +++++++++-------- libs/common/src/tools/rx.ts | 82 +- .../user-state-subject-dependency-provider.ts | 5 + .../tools/state/user-state-subject.spec.ts | 1 + .../src/tools/state/user-state-subject.ts | 7 +- libs/common/src/tools/types.ts | 5 + libs/common/src/tools/util.ts | 16 +- .../src/components/export.component.ts | 6 +- .../src/catchall-settings.component.ts | 19 +- .../credential-generator-history.component.ts | 19 +- .../src/credential-generator.component.html | 10 +- .../src/credential-generator.component.ts | 300 ++-- .../src/forwarder-settings.component.ts | 52 +- .../src/generator-services.module.ts | 144 +- .../src/passphrase-settings.component.ts | 88 +- .../src/password-generator.component.html | 5 +- .../src/password-generator.component.ts | 142 +- .../src/password-settings.component.ts | 86 +- .../src/subaddress-settings.component.ts | 20 +- .../src/username-generator.component.html | 6 +- .../src/username-generator.component.ts | 286 ++-- .../src/username-settings.component.ts | 19 +- libs/tools/generator/components/src/util.ts | 103 +- ...redential-generator-service.abstraction.ts | 104 ++ .../generator.service.abstraction.ts | 1 + .../generator/core/src/abstractions/index.ts | 1 + .../core/src/data/default-addy-io-options.ts | 8 - .../data/default-credential-preferences.ts | 18 - .../src/data/default-duck-duck-go-options.ts | 6 - .../core/src/data/default-fastmail-options.ts | 8 - .../src/data/default-firefox-relay-options.ts | 6 - .../src/data/default-forward-email-options.ts | 7 - .../src/data/default-simple-login-options.ts | 7 - .../core/src/data/generator-types.ts | 15 - .../generator/core/src/data/generators.ts | 422 ----- libs/tools/generator/core/src/data/index.ts | 12 - .../tools/generator/core/src/data/policies.ts | 23 - .../core/src/data/username-digits.ts | 4 - .../core/src/engine/email-randomizer.spec.ts | 21 +- .../core/src/engine/email-randomizer.ts | 5 +- .../generator/core/src/engine/forwarder.ts | 4 +- .../src/engine/password-randomizer.spec.ts | 11 +- .../core/src/engine/password-randomizer.ts | 5 +- .../src/engine/username-randomizer.spec.ts | 8 +- libs/tools/generator/core/src/index.ts | 23 +- .../generator/core/src/integration/addy-io.ts | 3 +- .../core/src/integration/duck-duck-go.ts | 3 +- .../core/src/integration/fastmail.ts | 3 +- .../core/src/integration/firefox-relay.ts | 3 +- .../core/src/integration/forward-email.ts | 3 +- .../core/src/integration/simple-login.ts | 3 +- .../core/src/metadata/algorithm-metadata.ts | 10 +- .../core/src/metadata/email/catchall.spec.ts | 3 +- .../core/src/metadata/email/catchall.ts | 9 +- .../core/src/metadata/email/forwarder.ts | 17 +- .../src/metadata/email/plus-address.spec.ts | 3 +- .../core/src/metadata/email/plus-address.ts | 9 +- .../core/src/metadata/generator-metadata.ts | 3 +- .../generator/core/src/metadata/index.ts | 42 +- .../metadata/password/eff-word-list.spec.ts | 3 +- .../src/metadata/password/eff-word-list.ts | 11 +- .../metadata/password/random-password.spec.ts | 3 +- .../src/metadata/password/random-password.ts | 9 +- .../metadata/username/eff-word-list.spec.ts | 3 +- .../src/metadata/username/eff-word-list.ts | 9 +- .../tools/generator/core/src/metadata/util.ts | 8 +- .../available-algorithms-constraint.ts | 76 + .../available-algorithms-policy.spec.ts | 18 +- .../policies/available-algorithms-policy.ts | 43 +- ...ynamic-password-policy-constraints.spec.ts | 50 +- .../generator/core/src/policies/index.ts | 2 + ...phrase-generator-options-evaluator.spec.ts | 70 +- .../passphrase-least-privilege.spec.ts | 20 +- .../passphrase-policy-constraints.spec.ts | 20 +- ...ssword-generator-options-evaluator.spec.ts | 126 +- .../policies/password-least-privilege.spec.ts | 24 +- .../credential-generator-providers.ts | 14 + .../providers/credential-preferences.spec.ts | 105 ++ .../src/providers/credential-preferences.ts | 28 + .../generator-dependency-provider.ts | 12 + .../generator-metadata-provider.spec.ts | 5 +- .../generator-metadata-provider.ts | 56 +- .../generator-profile-provider.spec.ts | 1 + .../generator-profile-provider.ts | 6 +- .../generator/core/src/providers/index.ts | 4 + libs/tools/generator/core/src/rx.ts | 14 - .../credential-generator.service.spec.ts | 1050 ------------ .../services/credential-generator.service.ts | 296 ---- .../services/credential-preferences.spec.ts | 53 - .../src/services/credential-preferences.ts | 30 - ...fault-credential-generator.service.spec.ts | 356 ++++ .../default-credential-generator.service.ts | 219 +++ .../default-generator.service.spec.ts | 12 +- .../generator/core/src/services/index.ts | 2 +- .../eff-username-generator-strategy.ts | 7 +- .../passphrase-generator-strategy.spec.ts | 31 +- .../passphrase-generator-strategy.ts | 14 +- .../password-generator-strategy.spec.ts | 53 +- .../strategies/password-generator-strategy.ts | 18 +- .../core/src/types/algorithm-info.ts | 50 + .../credential-generator-configuration.ts | 153 -- .../core/src/types/credential-preference.ts | 10 + .../core/src/types/forwarder-options.ts | 65 +- .../core/src/types/generate-request.ts | 11 +- .../src/types/generated-credential.spec.ts | 20 +- .../core/src/types/generated-credential.ts | 8 +- .../core/src/types/generator-type.ts | 83 - libs/tools/generator/core/src/types/index.ts | 16 +- .../core/src/types/policy-configuration.ts | 13 - libs/tools/generator/core/src/util.spec.ts | 90 +- libs/tools/generator/core/src/util.ts | 33 +- libs/tools/generator/core/tsconfig.json | 6 +- .../history/src/generated-credential.spec.ts | 22 +- .../history/src/generated-credential.ts | 4 +- .../src/generator-history.abstraction.ts | 4 +- .../local-generator-history.service.spec.ts | 54 +- .../src/local-generator-history.service.ts | 9 +- .../legacy/src}/forwarders.ts | 16 +- ...legacy-password-generation.service.spec.ts | 89 +- ...legacy-username-generation.service.spec.ts | 103 +- .../src/legacy-username-generation.service.ts | 19 +- .../generator-navigation-evaluator.spec.ts | 4 +- .../src/generator-navigation-evaluator.ts | 4 +- .../src/generator-navigation-policy.ts | 4 +- .../navigation/src/generator-navigation.ts | 7 +- .../options/send-options.component.ts | 6 +- 140 files changed, 3720 insertions(+), 4085 deletions(-) create mode 100644 libs/common/src/tools/log/disabled-logger.ts delete mode 100644 libs/common/src/tools/log/disabled-semantic-logger.ts create mode 100644 libs/common/src/tools/log/types.ts create mode 100644 libs/common/src/tools/log/util.ts create mode 100644 libs/common/src/tools/rx.rxjs.ts create mode 100644 libs/tools/generator/core/src/abstractions/credential-generator-service.abstraction.ts delete mode 100644 libs/tools/generator/core/src/data/default-addy-io-options.ts delete mode 100644 libs/tools/generator/core/src/data/default-credential-preferences.ts delete mode 100644 libs/tools/generator/core/src/data/default-duck-duck-go-options.ts delete mode 100644 libs/tools/generator/core/src/data/default-fastmail-options.ts delete mode 100644 libs/tools/generator/core/src/data/default-firefox-relay-options.ts delete mode 100644 libs/tools/generator/core/src/data/default-forward-email-options.ts delete mode 100644 libs/tools/generator/core/src/data/default-simple-login-options.ts delete mode 100644 libs/tools/generator/core/src/data/generator-types.ts delete mode 100644 libs/tools/generator/core/src/data/generators.ts delete mode 100644 libs/tools/generator/core/src/data/policies.ts delete mode 100644 libs/tools/generator/core/src/data/username-digits.ts create mode 100644 libs/tools/generator/core/src/policies/available-algorithms-constraint.ts create mode 100644 libs/tools/generator/core/src/providers/credential-generator-providers.ts create mode 100644 libs/tools/generator/core/src/providers/credential-preferences.spec.ts create mode 100644 libs/tools/generator/core/src/providers/credential-preferences.ts create mode 100644 libs/tools/generator/core/src/providers/generator-dependency-provider.ts rename libs/tools/generator/core/src/{services => providers}/generator-metadata-provider.spec.ts (98%) rename libs/tools/generator/core/src/{services => providers}/generator-metadata-provider.ts (87%) rename libs/tools/generator/core/src/{services => providers}/generator-profile-provider.spec.ts (99%) rename libs/tools/generator/core/src/{services => providers}/generator-profile-provider.ts (94%) create mode 100644 libs/tools/generator/core/src/providers/index.ts delete mode 100644 libs/tools/generator/core/src/services/credential-generator.service.spec.ts delete mode 100644 libs/tools/generator/core/src/services/credential-generator.service.ts delete mode 100644 libs/tools/generator/core/src/services/credential-preferences.spec.ts delete mode 100644 libs/tools/generator/core/src/services/credential-preferences.ts create mode 100644 libs/tools/generator/core/src/services/default-credential-generator.service.spec.ts create mode 100644 libs/tools/generator/core/src/services/default-credential-generator.service.ts create mode 100644 libs/tools/generator/core/src/types/algorithm-info.ts delete mode 100644 libs/tools/generator/core/src/types/credential-generator-configuration.ts create mode 100644 libs/tools/generator/core/src/types/credential-preference.ts delete mode 100644 libs/tools/generator/core/src/types/generator-type.ts rename libs/tools/generator/{core/src/data => extensions/legacy/src}/forwarders.ts (73%) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 4775d1f7af0..feb5a7706f3 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 631f7d571f6..9d668d464ae 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Other" }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, diff --git a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts index f11b14aea38..26f87f333eb 100644 --- a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts @@ -7,7 +7,7 @@ import { BehaviorSubject, map } from "rxjs"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { Generators } from "@bitwarden/generator-core"; +import { BuiltIn, Profile } from "@bitwarden/generator-core"; import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; @@ -26,14 +26,22 @@ export class PasswordGeneratorPolicy extends BasePolicy { export class PasswordGeneratorPolicyComponent extends BasePolicyComponent { // these properties forward the application default settings to the UI // for HTML attribute bindings - protected readonly minLengthMin = Generators.password.settings.constraints.length.min; - protected readonly minLengthMax = Generators.password.settings.constraints.length.max; - protected readonly minNumbersMin = Generators.password.settings.constraints.minNumber.min; - protected readonly minNumbersMax = Generators.password.settings.constraints.minNumber.max; - protected readonly minSpecialMin = Generators.password.settings.constraints.minSpecial.min; - protected readonly minSpecialMax = Generators.password.settings.constraints.minSpecial.max; - protected readonly minNumberWordsMin = Generators.passphrase.settings.constraints.numWords.min; - protected readonly minNumberWordsMax = Generators.passphrase.settings.constraints.numWords.max; + protected readonly minLengthMin = + BuiltIn.password.profiles[Profile.account].constraints.default.length.min; + protected readonly minLengthMax = + BuiltIn.password.profiles[Profile.account].constraints.default.length.max; + protected readonly minNumbersMin = + BuiltIn.password.profiles[Profile.account].constraints.default.minNumber.min; + protected readonly minNumbersMax = + BuiltIn.password.profiles[Profile.account].constraints.default.minNumber.max; + protected readonly minSpecialMin = + BuiltIn.password.profiles[Profile.account].constraints.default.minSpecial.min; + protected readonly minSpecialMax = + BuiltIn.password.profiles[Profile.account].constraints.default.minSpecial.max; + protected readonly minNumberWordsMin = + BuiltIn.passphrase.profiles[Profile.account].constraints.default.numWords.min; + protected readonly minNumberWordsMax = + BuiltIn.passphrase.profiles[Profile.account].constraints.default.numWords.max; data = this.formBuilder.group({ overridePasswordType: [null], diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 3c17baabbb9..a170612fab2 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6825,6 +6819,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6888,6 +6900,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/libs/common/src/tools/extension/extension.service.spec.ts b/libs/common/src/tools/extension/extension.service.spec.ts index dad5684b523..9959488feca 100644 --- a/libs/common/src/tools/extension/extension.service.spec.ts +++ b/libs/common/src/tools/extension/extension.service.spec.ts @@ -60,6 +60,7 @@ const SomeProvider = { } as LegacyEncryptorProvider, state: SomeStateProvider, log: disabledSemanticLoggerProvider, + now: Date.now, } as UserStateSubjectDependencyProvider; const SomeExtension: ExtensionMetadata = { diff --git a/libs/common/src/tools/log/disabled-logger.ts b/libs/common/src/tools/log/disabled-logger.ts new file mode 100644 index 00000000000..53feb0c8b39 --- /dev/null +++ b/libs/common/src/tools/log/disabled-logger.ts @@ -0,0 +1,24 @@ +import { Jsonify } from "type-fest"; + +import { deepFreeze } from "../util"; + +import { SemanticLogger } from "./semantic-logger.abstraction"; + +/** All disabled loggers emitted by this module are `===` to this logger. */ +export const DISABLED_LOGGER: SemanticLogger = deepFreeze({ + debug(_content: Jsonify, _message?: string): void {}, + + info(_content: Jsonify, _message?: string): void {}, + + warn(_content: Jsonify, _message?: string): void {}, + + error(_content: Jsonify, _message?: string): void {}, + + panic(content: Jsonify, message?: string): never { + if (typeof content === "string" && !message) { + throw new Error(content); + } else { + throw new Error(message); + } + }, +}); diff --git a/libs/common/src/tools/log/disabled-semantic-logger.ts b/libs/common/src/tools/log/disabled-semantic-logger.ts deleted file mode 100644 index 21ea48bbe51..00000000000 --- a/libs/common/src/tools/log/disabled-semantic-logger.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Jsonify } from "type-fest"; - -import { SemanticLogger } from "./semantic-logger.abstraction"; - -/** Disables semantic logs. Still panics. */ -export class DisabledSemanticLogger implements SemanticLogger { - debug(_content: Jsonify, _message?: string): void {} - - info(_content: Jsonify, _message?: string): void {} - - warn(_content: Jsonify, _message?: string): void {} - - error(_content: Jsonify, _message?: string): void {} - - panic(content: Jsonify, message?: string): never { - if (typeof content === "string" && !message) { - throw new Error(content); - } else { - throw new Error(message); - } - } -} diff --git a/libs/common/src/tools/log/factory.ts b/libs/common/src/tools/log/factory.ts index f8abc4d2240..5f0f2d74e91 100644 --- a/libs/common/src/tools/log/factory.ts +++ b/libs/common/src/tools/log/factory.ts @@ -3,11 +3,10 @@ import { Jsonify } from "type-fest"; import { LogService } from "../../platform/abstractions/log.service"; import { DefaultSemanticLogger } from "./default-semantic-logger"; -import { DisabledSemanticLogger } from "./disabled-semantic-logger"; +import { DISABLED_LOGGER } from "./disabled-logger"; import { SemanticLogger } from "./semantic-logger.abstraction"; - -/** A type for injection of a log provider */ -export type LogProvider = (context: Jsonify) => SemanticLogger; +import { LogProvider } from "./types"; +import { warnLoggingEnabled } from "./util"; /** Instantiates a semantic logger that emits nothing when a message * is logged. @@ -18,38 +17,72 @@ export type LogProvider = (context: Jsonify) => SemanticLogger export function disabledSemanticLoggerProvider( _context: Jsonify, ): SemanticLogger { - return new DisabledSemanticLogger(); + return DISABLED_LOGGER; } /** Instantiates a semantic logger that emits logs to the console. - * @param context a static payload that is cloned when the logger - * logs a message. The `messages`, `level`, and `content` fields - * are reserved for use by loggers. - * @param settings specializes how the semantic logger functions. - * If this is omitted, the logger suppresses debug messages. + * @param logService writes semantic logs to the console */ -export function consoleSemanticLoggerProvider( - logger: LogService, - context: Jsonify, -): SemanticLogger { - return new DefaultSemanticLogger(logger, context); +export function consoleSemanticLoggerProvider(logService: LogService): LogProvider { + function provider(context: Jsonify) { + const logger = new DefaultSemanticLogger(logService, context); + + warnLoggingEnabled(logService, "consoleSemanticLoggerProvider", context); + return logger; + } + + return provider; } -/** Instantiates a semantic logger that emits logs to the console. +/** Instantiates a semantic logger that emits logs to the console when the + * context's `type` matches its values. + * @param logService writes semantic logs to the console + * @param types the values to match against + */ +export function enableLogForTypes(logService: LogService, types: string[]): LogProvider { + if (types.length) { + warnLoggingEnabled(logService, "enableLogForTypes", { types }); + } + + function provider(context: Jsonify) { + const { type } = context as { type?: unknown }; + if (typeof type === "string" && types.includes(type)) { + const logger = new DefaultSemanticLogger(logService, context); + + warnLoggingEnabled(logService, "enableLogForTypes", { + targetType: type, + available: types, + loggerContext: context, + }); + return logger; + } else { + return DISABLED_LOGGER; + } + } + + return provider; +} + +/** Instantiates a semantic logger that emits logs to the console when its enabled. + * @param enable logs are emitted when this is true + * @param logService writes semantic logs to the console * @param context a static payload that is cloned when the logger - * logs a message. The `messages`, `level`, and `content` fields - * are reserved for use by loggers. - * @param settings specializes how the semantic logger functions. - * If this is omitted, the logger suppresses debug messages. + * logs a message. + * + * @remarks The `message`, `level`, `provider`, and `content` fields + * are reserved for use by the semantic logging system. */ export function ifEnabledSemanticLoggerProvider( enable: boolean, - logger: LogService, + logService: LogService, context: Jsonify, ) { if (enable) { - return consoleSemanticLoggerProvider(logger, context); + const logger = new DefaultSemanticLogger(logService, context); + + warnLoggingEnabled(logService, "ifEnabledSemanticLoggerProvider", context); + return logger; } else { - return disabledSemanticLoggerProvider(context); + return DISABLED_LOGGER; } } diff --git a/libs/common/src/tools/log/index.ts b/libs/common/src/tools/log/index.ts index 22444d23e27..1c93a6ce63f 100644 --- a/libs/common/src/tools/log/index.ts +++ b/libs/common/src/tools/log/index.ts @@ -1,2 +1,4 @@ export * from "./factory"; +export * from "./disabled-logger"; +export { LogProvider } from "./types"; export { SemanticLogger } from "./semantic-logger.abstraction"; diff --git a/libs/common/src/tools/log/types.ts b/libs/common/src/tools/log/types.ts new file mode 100644 index 00000000000..ca887af4225 --- /dev/null +++ b/libs/common/src/tools/log/types.ts @@ -0,0 +1,11 @@ +import { Jsonify } from "type-fest"; + +import { SemanticLogger } from "./semantic-logger.abstraction"; + +/** Creates a semantic logger. + * @param context all logs emitted by the logger are extended with + * these fields. + * @remarks The `message`, `level`, `provider`, and `content` fields + * are reserved for use by the semantic logging system. + */ +export type LogProvider = (context: Jsonify) => SemanticLogger; diff --git a/libs/common/src/tools/log/util.ts b/libs/common/src/tools/log/util.ts new file mode 100644 index 00000000000..cf1c39230e1 --- /dev/null +++ b/libs/common/src/tools/log/util.ts @@ -0,0 +1,12 @@ +import { LogService } from "../../platform/abstractions/log.service"; + +// show our GRIT - these functions implement generalized logging +// controls and should return DISABLED_LOGGER in production. +export function warnLoggingEnabled(logService: LogService, method: string, context?: any) { + logService.warning({ + method, + context, + provider: "tools/log", + message: "Semantic logging enabled. 🦟 Please report this bug if you see it 🦟", + }); +} diff --git a/libs/common/src/tools/private-classifier.ts b/libs/common/src/tools/private-classifier.ts index e2406d314c0..58244ae9906 100644 --- a/libs/common/src/tools/private-classifier.ts +++ b/libs/common/src/tools/private-classifier.ts @@ -17,7 +17,7 @@ export class PrivateClassifier implements Classifier; - return { disclosed: {}, secret }; + return { disclosed: null, secret }; } declassify(_disclosed: Jsonify>, secret: Jsonify) { diff --git a/libs/common/src/tools/public-classifier.ts b/libs/common/src/tools/public-classifier.ts index 136bee555ac..e036ebd1c42 100644 --- a/libs/common/src/tools/public-classifier.ts +++ b/libs/common/src/tools/public-classifier.ts @@ -16,7 +16,7 @@ export class PublicClassifier implements Classifier; - return { disclosed, secret: "" }; + return { disclosed, secret: null }; } declassify(disclosed: Jsonify, _secret: Jsonify>) { diff --git a/libs/common/src/tools/rx.rxjs.ts b/libs/common/src/tools/rx.rxjs.ts new file mode 100644 index 00000000000..7c11d658f19 --- /dev/null +++ b/libs/common/src/tools/rx.rxjs.ts @@ -0,0 +1,13 @@ +import { Observable } from "rxjs"; + +/** + * Used to infer types from arguments to functions like {@link withLatestReady}. + * So that you can have `forkJoin([Observable, PromiseLike]): Observable<[A, B]>` + * et al. + * @remarks this type definition is derived from rxjs' {@link ObservableInputTuple}. + * The difference is it *only* works with observables, while the rx version works + * with any thing that can become an observable. + */ +export type ObservableTuple = { + [K in keyof T]: Observable; +}; diff --git a/libs/common/src/tools/rx.spec.ts b/libs/common/src/tools/rx.spec.ts index ee1de1c9118..5177dcaa2a5 100644 --- a/libs/common/src/tools/rx.spec.ts +++ b/libs/common/src/tools/rx.spec.ts @@ -3,7 +3,7 @@ * @jest-environment ../../../../shared/test.environment.ts */ // @ts-strict-ignore this file explicitly tests what happens when types are ignored -import { of, firstValueFrom, Subject, tap, EmptyError } from "rxjs"; +import { of, firstValueFrom, Subject, tap, EmptyError, BehaviorSubject } from "rxjs"; import { awaitAsync, trackEmissions } from "../../spec"; @@ -16,733 +16,825 @@ import { reduceCollection, withLatestReady, pin, + memoizedMap, } from "./rx"; -describe("errorOnChange", () => { - it("emits a single value when the input emits only once", async () => { - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(errorOnChange()).subscribe((v) => results.push(v)); +describe("tools rx utilites", () => { + describe("errorOnChange", () => { + it("emits a single value when the input emits only once", async () => { + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(errorOnChange()).subscribe((v) => results.push(v)); - source$.next(1); + source$.next(1); - expect(results).toEqual([1]); + expect(results).toEqual([1]); + }); + + it("emits when the input emits", async () => { + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(errorOnChange()).subscribe((v) => results.push(v)); + + source$.next(1); + source$.next(1); + + expect(results).toEqual([1, 1]); + }); + + it("errors when the input errors", async () => { + const source$ = new Subject(); + const expected = {}; + let error: any = null; + source$.pipe(errorOnChange()).subscribe({ error: (v: unknown) => (error = v) }); + + source$.error(expected); + + expect(error).toBe(expected); + }); + + it("completes when the input completes", async () => { + const source$ = new Subject(); + let complete: boolean = false; + source$.pipe(errorOnChange()).subscribe({ complete: () => (complete = true) }); + + source$.complete(); + + expect(complete).toBe(true); + }); + + it("errors when the input changes", async () => { + const source$ = new Subject(); + let error: any = null; + source$.pipe(errorOnChange()).subscribe({ error: (v: unknown) => (error = v) }); + + source$.next(1); + source$.next(2); + + expect(error).toEqual({ expectedValue: 1, actualValue: 2 }); + }); + + it("emits when the extracted value remains constant", async () => { + type Foo = { foo: string }; + const source$ = new Subject(); + const results: Foo[] = []; + source$.pipe(errorOnChange((v) => v.foo)).subscribe((v) => results.push(v)); + + source$.next({ foo: "bar" }); + source$.next({ foo: "bar" }); + + expect(results).toEqual([{ foo: "bar" }, { foo: "bar" }]); + }); + + it("errors when an extracted value changes", async () => { + type Foo = { foo: string }; + const source$ = new Subject(); + let error: any = null; + source$.pipe(errorOnChange((v) => v.foo)).subscribe({ error: (v: unknown) => (error = v) }); + + source$.next({ foo: "bar" }); + source$.next({ foo: "baz" }); + + expect(error).toEqual({ expectedValue: "bar", actualValue: "baz" }); + }); + + it("constructs an error when the extracted value changes", async () => { + type Foo = { foo: string }; + const source$ = new Subject(); + let error: any = null; + source$ + .pipe( + errorOnChange( + (v) => v.foo, + (expected, actual) => ({ expected, actual }), + ), + ) + .subscribe({ error: (v: unknown) => (error = v) }); + + source$.next({ foo: "bar" }); + source$.next({ foo: "baz" }); + + expect(error).toEqual({ expected: "bar", actual: "baz" }); + }); }); - it("emits when the input emits", async () => { - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(errorOnChange()).subscribe((v) => results.push(v)); + describe("reduceCollection", () => { + it.each([[null], [undefined], [[]]])( + "should return the default value when the collection is %p", + async (value: number[]) => { + const reduce = (acc: number, value: number) => acc + value; + const source$ = of(value); - source$.next(1); - source$.next(1); + const result$ = source$.pipe(reduceCollection(reduce, 100)); + const result = await firstValueFrom(result$); - expect(results).toEqual([1, 1]); - }); + expect(result).toEqual(100); + }, + ); - it("errors when the input errors", async () => { - const source$ = new Subject(); - const expected = {}; - let error: any = null; - source$.pipe(errorOnChange()).subscribe({ error: (v: unknown) => (error = v) }); - - source$.error(expected); - - expect(error).toBe(expected); - }); - - it("completes when the input completes", async () => { - const source$ = new Subject(); - let complete: boolean = false; - source$.pipe(errorOnChange()).subscribe({ complete: () => (complete = true) }); - - source$.complete(); - - expect(complete).toBe(true); - }); - - it("errors when the input changes", async () => { - const source$ = new Subject(); - let error: any = null; - source$.pipe(errorOnChange()).subscribe({ error: (v: unknown) => (error = v) }); - - source$.next(1); - source$.next(2); - - expect(error).toEqual({ expectedValue: 1, actualValue: 2 }); - }); - - it("emits when the extracted value remains constant", async () => { - type Foo = { foo: string }; - const source$ = new Subject(); - const results: Foo[] = []; - source$.pipe(errorOnChange((v) => v.foo)).subscribe((v) => results.push(v)); - - source$.next({ foo: "bar" }); - source$.next({ foo: "bar" }); - - expect(results).toEqual([{ foo: "bar" }, { foo: "bar" }]); - }); - - it("errors when an extracted value changes", async () => { - type Foo = { foo: string }; - const source$ = new Subject(); - let error: any = null; - source$.pipe(errorOnChange((v) => v.foo)).subscribe({ error: (v: unknown) => (error = v) }); - - source$.next({ foo: "bar" }); - source$.next({ foo: "baz" }); - - expect(error).toEqual({ expectedValue: "bar", actualValue: "baz" }); - }); - - it("constructs an error when the extracted value changes", async () => { - type Foo = { foo: string }; - const source$ = new Subject(); - let error: any = null; - source$ - .pipe( - errorOnChange( - (v) => v.foo, - (expected, actual) => ({ expected, actual }), - ), - ) - .subscribe({ error: (v: unknown) => (error = v) }); - - source$.next({ foo: "bar" }); - source$.next({ foo: "baz" }); - - expect(error).toEqual({ expected: "bar", actual: "baz" }); - }); -}); - -describe("reduceCollection", () => { - it.each([[null], [undefined], [[]]])( - "should return the default value when the collection is %p", - async (value: number[]) => { + it("should reduce the collection to a single value", async () => { const reduce = (acc: number, value: number) => acc + value; - const source$ = of(value); + const source$ = of([1, 2, 3]); - const result$ = source$.pipe(reduceCollection(reduce, 100)); + const result$ = source$.pipe(reduceCollection(reduce, 0)); const result = await firstValueFrom(result$); - expect(result).toEqual(100); - }, - ); - - it("should reduce the collection to a single value", async () => { - const reduce = (acc: number, value: number) => acc + value; - const source$ = of([1, 2, 3]); - - const result$ = source$.pipe(reduceCollection(reduce, 0)); - const result = await firstValueFrom(result$); - - expect(result).toEqual(6); - }); -}); - -describe("distinctIfShallowMatch", () => { - it("emits a single value", async () => { - const source$ = of({ foo: true }); - const pipe$ = source$.pipe(distinctIfShallowMatch()); - - const result = trackEmissions(pipe$); - await awaitAsync(); - - expect(result).toEqual([{ foo: true }]); - }); - - it("emits different values", async () => { - const source$ = of({ foo: true }, { foo: false }); - const pipe$ = source$.pipe(distinctIfShallowMatch()); - - const result = trackEmissions(pipe$); - await awaitAsync(); - - expect(result).toEqual([{ foo: true }, { foo: false }]); - }); - - it("emits new keys", async () => { - const source$ = of({ foo: true }, { foo: true, bar: true }); - const pipe$ = source$.pipe(distinctIfShallowMatch()); - - const result = trackEmissions(pipe$); - await awaitAsync(); - - expect(result).toEqual([{ foo: true }, { foo: true, bar: true }]); - }); - - it("suppresses identical values", async () => { - const source$ = of({ foo: true }, { foo: true }); - const pipe$ = source$.pipe(distinctIfShallowMatch()); - - const result = trackEmissions(pipe$); - await awaitAsync(); - - expect(result).toEqual([{ foo: true }]); - }); - - it("suppresses removed keys", async () => { - const source$ = of({ foo: true, bar: true }, { foo: true }); - const pipe$ = source$.pipe(distinctIfShallowMatch()); - - const result = trackEmissions(pipe$); - await awaitAsync(); - - expect(result).toEqual([{ foo: true, bar: true }]); - }); -}); - -describe("anyComplete", () => { - it("emits true when its input completes", () => { - const input$ = new Subject(); - - const emissions: boolean[] = []; - anyComplete(input$).subscribe((e) => emissions.push(e)); - input$.complete(); - - expect(emissions).toEqual([true]); - }); - - it("completes when its input is already complete", () => { - const input = new Subject(); - input.complete(); - - let completed = false; - anyComplete(input).subscribe({ complete: () => (completed = true) }); - - expect(completed).toBe(true); - }); - - it("completes when any input completes", () => { - const input$ = new Subject(); - const completing$ = new Subject(); - - let completed = false; - anyComplete([input$, completing$]).subscribe({ complete: () => (completed = true) }); - completing$.complete(); - - expect(completed).toBe(true); - }); - - it("ignores emissions", () => { - const input$ = new Subject(); - - const emissions: boolean[] = []; - anyComplete(input$).subscribe((e) => emissions.push(e)); - input$.next(1); - input$.next(2); - input$.complete(); - - expect(emissions).toEqual([true]); - }); - - it("forwards errors", () => { - const input$ = new Subject(); - const expected = { some: "error" }; - - let error = null; - anyComplete(input$).subscribe({ error: (e: unknown) => (error = e) }); - input$.error(expected); - - expect(error).toEqual(expected); - }); -}); - -describe("ready", () => { - it("connects when subscribed", () => { - const watch$ = new Subject(); - let connected = false; - const source$ = new Subject().pipe(tap({ subscribe: () => (connected = true) })); - - // precondition: ready$ should be cold - const ready$ = source$.pipe(ready(watch$)); - expect(connected).toBe(false); - - ready$.subscribe(); - - expect(connected).toBe(true); - }); - - it("suppresses source emissions until its watch emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - const results: number[] = []; - ready$.subscribe((n) => results.push(n)); - - // precondition: no emissions - source$.next(1); - expect(results).toEqual([]); - - watch$.next(); - - expect(results).toEqual([1]); - }); - - it("suppresses source emissions until all watches emit", () => { - const watchA$ = new Subject(); - const watchB$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready([watchA$, watchB$])); - const results: number[] = []; - ready$.subscribe((n) => results.push(n)); - - // preconditions: no emissions - source$.next(1); - expect(results).toEqual([]); - watchA$.next(); - expect(results).toEqual([]); - - watchB$.next(); - - expect(results).toEqual([1]); - }); - - it("emits the last source emission when its watch emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - const results: number[] = []; - ready$.subscribe((n) => results.push(n)); - - // precondition: no emissions - source$.next(1); - expect(results).toEqual([]); - - source$.next(2); - watch$.next(); - - expect(results).toEqual([2]); - }); - - it("emits all source emissions after its watch emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - const results: number[] = []; - ready$.subscribe((n) => results.push(n)); - - watch$.next(); - source$.next(1); - source$.next(2); - - expect(results).toEqual([1, 2]); - }); - - it("ignores repeated watch emissions", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - const results: number[] = []; - ready$.subscribe((n) => results.push(n)); - - watch$.next(); - source$.next(1); - watch$.next(); - source$.next(2); - watch$.next(); - - expect(results).toEqual([1, 2]); - }); - - it("completes when its source completes", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - let completed = false; - ready$.subscribe({ complete: () => (completed = true) }); - - source$.complete(); - - expect(completed).toBeTruthy(); - }); - - it("errors when its source errors", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - const expected = { some: "error" }; - let error = null; - ready$.subscribe({ error: (e: unknown) => (error = e) }); - - source$.error(expected); - - expect(error).toEqual(expected); - }); - - it("errors when its watch errors", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - const expected = { some: "error" }; - let error = null; - ready$.subscribe({ error: (e: unknown) => (error = e) }); - - watch$.error(expected); - - expect(error).toEqual(expected); - }); - - it("errors when its watch completes before emitting", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(ready(watch$)); - let error = null; - ready$.subscribe({ error: (e: unknown) => (error = e) }); - - watch$.complete(); - - expect(error).toBeInstanceOf(EmptyError); - }); -}); - -describe("withLatestReady", () => { - it("connects when subscribed", () => { - const watch$ = new Subject(); - let connected = false; - const source$ = new Subject().pipe(tap({ subscribe: () => (connected = true) })); - - // precondition: ready$ should be cold - const ready$ = source$.pipe(withLatestReady(watch$)); - expect(connected).toBe(false); - - ready$.subscribe(); - - expect(connected).toBe(true); - }); - - it("suppresses source emissions until its watch emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - const results: [number, string][] = []; - ready$.subscribe((n) => results.push(n)); - - // precondition: no emissions - source$.next(1); - expect(results).toEqual([]); - - watch$.next("watch"); - - expect(results).toEqual([[1, "watch"]]); - }); - - it("emits the last source emission when its watch emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - const results: [number, string][] = []; - ready$.subscribe((n) => results.push(n)); - - // precondition: no emissions - source$.next(1); - expect(results).toEqual([]); - - source$.next(2); - watch$.next("watch"); - - expect(results).toEqual([[2, "watch"]]); - }); - - it("emits all source emissions after its watch emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - const results: [number, string][] = []; - ready$.subscribe((n) => results.push(n)); - - watch$.next("watch"); - source$.next(1); - source$.next(2); - - expect(results).toEqual([ - [1, "watch"], - [2, "watch"], - ]); - }); - - it("appends the latest watch emission", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - const results: [number, string][] = []; - ready$.subscribe((n) => results.push(n)); - - watch$.next("ignored"); - watch$.next("watch"); - source$.next(1); - watch$.next("ignored"); - watch$.next("watch"); - source$.next(2); - - expect(results).toEqual([ - [1, "watch"], - [2, "watch"], - ]); - }); - - it("completes when its source completes", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - let completed = false; - ready$.subscribe({ complete: () => (completed = true) }); - - source$.complete(); - - expect(completed).toBeTruthy(); - }); - - it("errors when its source errors", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - const expected = { some: "error" }; - let error = null; - ready$.subscribe({ error: (e: unknown) => (error = e) }); - - source$.error(expected); - - expect(error).toEqual(expected); - }); - - it("errors when its watch errors", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - const expected = { some: "error" }; - let error = null; - ready$.subscribe({ error: (e: unknown) => (error = e) }); - - watch$.error(expected); - - expect(error).toEqual(expected); - }); - - it("errors when its watch completes before emitting", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const ready$ = source$.pipe(withLatestReady(watch$)); - let error = null; - ready$.subscribe({ error: (e: unknown) => (error = e) }); - - watch$.complete(); - - expect(error).toBeInstanceOf(EmptyError); - }); -}); - -describe("on", () => { - it("connects when subscribed", () => { - const watch$ = new Subject(); - let connected = false; - const source$ = new Subject().pipe(tap({ subscribe: () => (connected = true) })); - - // precondition: on$ should be cold - const on$ = source$.pipe(on(watch$)); - expect(connected).toBeFalsy(); - - on$.subscribe(); - - expect(connected).toBeTruthy(); - }); - - it("suppresses source emissions until `on` emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(on(watch$)).subscribe((n) => results.push(n)); - - // precondition: on$ should be cold - source$.next(1); - expect(results).toEqual([]); - - watch$.next(); - - expect(results).toEqual([1]); - }); - - it("repeats source emissions when `on` emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(on(watch$)).subscribe((n) => results.push(n)); - source$.next(1); - - watch$.next(); - watch$.next(); - - expect(results).toEqual([1, 1]); - }); - - it("updates source emissions when `on` emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(on(watch$)).subscribe((n) => results.push(n)); - - source$.next(1); - watch$.next(); - source$.next(2); - watch$.next(); - - expect(results).toEqual([1, 2]); - }); - - it("emits a value when `on` emits before the source is ready", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(on(watch$)).subscribe((n) => results.push(n)); - - watch$.next(); - source$.next(1); - - expect(results).toEqual([1]); - }); - - it("ignores repeated `on` emissions before the source is ready", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(on(watch$)).subscribe((n) => results.push(n)); - - watch$.next(); - watch$.next(); - source$.next(1); - - expect(results).toEqual([1]); - }); - - it("emits only the latest source emission when `on` emits", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const results: number[] = []; - source$.pipe(on(watch$)).subscribe((n) => results.push(n)); - source$.next(1); - - watch$.next(); - - source$.next(2); - source$.next(3); - watch$.next(); - - expect(results).toEqual([1, 3]); - }); - - it("completes when its source completes", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - let complete: boolean = false; - source$.pipe(on(watch$)).subscribe({ complete: () => (complete = true) }); - - source$.complete(); - - expect(complete).toBeTruthy(); - }); - - it("completes when its watch completes", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - let complete: boolean = false; - source$.pipe(on(watch$)).subscribe({ complete: () => (complete = true) }); - - watch$.complete(); - - expect(complete).toBeTruthy(); - }); - - it("errors when its source errors", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const expected = { some: "error" }; - let error = null; - source$.pipe(on(watch$)).subscribe({ error: (e: unknown) => (error = e) }); - - source$.error(expected); - - expect(error).toEqual(expected); - }); - - it("errors when its watch errors", () => { - const watch$ = new Subject(); - const source$ = new Subject(); - const expected = { some: "error" }; - let error = null; - source$.pipe(on(watch$)).subscribe({ error: (e: unknown) => (error = e) }); - - watch$.error(expected); - - expect(error).toEqual(expected); - }); -}); - -describe("pin", () => { - it("emits the first value", async () => { - const input = new Subject(); - const result: unknown[] = []; - - input.pipe(pin()).subscribe((v) => result.push(v)); - input.next(1); - - expect(result).toEqual([1]); - }); - - it("filters repeated emissions", async () => { - const input = new Subject(); - const result: unknown[] = []; - - input.pipe(pin({ distinct: (p, c) => p == c })).subscribe((v) => result.push(v)); - input.next(1); - input.next(1); - - expect(result).toEqual([1]); - }); - - it("errors if multiple emissions occur", async () => { - const input = new Subject(); - let error: any = null!; - - input.pipe(pin()).subscribe({ - error: (e: unknown) => { - error = e; - }, + expect(result).toEqual(6); }); - input.next(1); - input.next(1); - - expect(error).toBeInstanceOf(Error); - expect(error.message).toMatch(/^unknown/); }); - it("names the pinned observables if multiple emissions occur", async () => { - const input = new Subject(); - let error: any = null!; + describe("distinctIfShallowMatch", () => { + it("emits a single value", async () => { + const source$ = of({ foo: true }); + const pipe$ = source$.pipe(distinctIfShallowMatch()); - input.pipe(pin({ name: () => "example" })).subscribe({ - error: (e: unknown) => { - error = e; - }, + const result = trackEmissions(pipe$); + await awaitAsync(); + + expect(result).toEqual([{ foo: true }]); }); - input.next(1); - input.next(1); - expect(error).toBeInstanceOf(Error); - expect(error.message).toMatch(/^example/); + it("emits different values", async () => { + const source$ = of({ foo: true }, { foo: false }); + const pipe$ = source$.pipe(distinctIfShallowMatch()); + + const result = trackEmissions(pipe$); + await awaitAsync(); + + expect(result).toEqual([{ foo: true }, { foo: false }]); + }); + + it("emits new keys", async () => { + const source$ = of({ foo: true }, { foo: true, bar: true }); + const pipe$ = source$.pipe(distinctIfShallowMatch()); + + const result = trackEmissions(pipe$); + await awaitAsync(); + + expect(result).toEqual([{ foo: true }, { foo: true, bar: true }]); + }); + + it("suppresses identical values", async () => { + const source$ = of({ foo: true }, { foo: true }); + const pipe$ = source$.pipe(distinctIfShallowMatch()); + + const result = trackEmissions(pipe$); + await awaitAsync(); + + expect(result).toEqual([{ foo: true }]); + }); + + it("suppresses removed keys", async () => { + const source$ = of({ foo: true, bar: true }, { foo: true }); + const pipe$ = source$.pipe(distinctIfShallowMatch()); + + const result = trackEmissions(pipe$); + await awaitAsync(); + + expect(result).toEqual([{ foo: true, bar: true }]); + }); }); - it("errors if indistinct emissions occur", async () => { - const input = new Subject(); - let error: any = null!; + describe("anyComplete", () => { + it("emits true when its input completes", () => { + const input$ = new Subject(); - input - .pipe(pin({ distinct: (p, c) => p == c })) - .subscribe({ error: (e: unknown) => (error = e) }); - input.next(1); - input.next(2); + const emissions: boolean[] = []; + anyComplete(input$).subscribe((e) => emissions.push(e)); + input$.complete(); - expect(error).toBeInstanceOf(Error); - expect(error.message).toMatch(/^unknown/); + expect(emissions).toEqual([true]); + }); + + it("completes when its input is already complete", () => { + const input = new Subject(); + input.complete(); + + let completed = false; + anyComplete(input).subscribe({ complete: () => (completed = true) }); + + expect(completed).toBe(true); + }); + + it("completes when any input completes", () => { + const input$ = new Subject(); + const completing$ = new Subject(); + + let completed = false; + anyComplete([input$, completing$]).subscribe({ complete: () => (completed = true) }); + completing$.complete(); + + expect(completed).toBe(true); + }); + + it("ignores emissions", () => { + const input$ = new Subject(); + + const emissions: boolean[] = []; + anyComplete(input$).subscribe((e) => emissions.push(e)); + input$.next(1); + input$.next(2); + input$.complete(); + + expect(emissions).toEqual([true]); + }); + + it("forwards errors", () => { + const input$ = new Subject(); + const expected = { some: "error" }; + + let error = null; + anyComplete(input$).subscribe({ error: (e: unknown) => (error = e) }); + input$.error(expected); + + expect(error).toEqual(expected); + }); + }); + + describe("ready", () => { + it("connects when subscribed", () => { + const watch$ = new Subject(); + let connected = false; + const source$ = new Subject().pipe(tap({ subscribe: () => (connected = true) })); + + // precondition: ready$ should be cold + const ready$ = source$.pipe(ready(watch$)); + expect(connected).toBe(false); + + ready$.subscribe(); + + expect(connected).toBe(true); + }); + + it("suppresses source emissions until its watch emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + const results: number[] = []; + ready$.subscribe((n) => results.push(n)); + + // precondition: no emissions + source$.next(1); + expect(results).toEqual([]); + + watch$.next(); + + expect(results).toEqual([1]); + }); + + it("suppresses source emissions until all watches emit", () => { + const watchA$ = new Subject(); + const watchB$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready([watchA$, watchB$])); + const results: number[] = []; + ready$.subscribe((n) => results.push(n)); + + // preconditions: no emissions + source$.next(1); + expect(results).toEqual([]); + watchA$.next(); + expect(results).toEqual([]); + + watchB$.next(); + + expect(results).toEqual([1]); + }); + + it("emits the last source emission when its watch emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + const results: number[] = []; + ready$.subscribe((n) => results.push(n)); + + // precondition: no emissions + source$.next(1); + expect(results).toEqual([]); + + source$.next(2); + watch$.next(); + + expect(results).toEqual([2]); + }); + + it("emits all source emissions after its watch emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + const results: number[] = []; + ready$.subscribe((n) => results.push(n)); + + watch$.next(); + source$.next(1); + source$.next(2); + + expect(results).toEqual([1, 2]); + }); + + it("ignores repeated watch emissions", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + const results: number[] = []; + ready$.subscribe((n) => results.push(n)); + + watch$.next(); + source$.next(1); + watch$.next(); + source$.next(2); + watch$.next(); + + expect(results).toEqual([1, 2]); + }); + + it("completes when its source completes", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + let completed = false; + ready$.subscribe({ complete: () => (completed = true) }); + + source$.complete(); + + expect(completed).toBeTruthy(); + }); + + it("errors when its source errors", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + const expected = { some: "error" }; + let error = null; + ready$.subscribe({ error: (e: unknown) => (error = e) }); + + source$.error(expected); + + expect(error).toEqual(expected); + }); + + it("errors when its watch errors", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + const expected = { some: "error" }; + let error = null; + ready$.subscribe({ error: (e: unknown) => (error = e) }); + + watch$.error(expected); + + expect(error).toEqual(expected); + }); + + it("errors when its watch completes before emitting", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(ready(watch$)); + let error = null; + ready$.subscribe({ error: (e: unknown) => (error = e) }); + + watch$.complete(); + + expect(error).toBeInstanceOf(EmptyError); + }); + }); + + describe("withLatestReady", () => { + it("connects when subscribed", () => { + const watch$ = new Subject(); + let connected = false; + const source$ = new Subject().pipe(tap({ subscribe: () => (connected = true) })); + + // precondition: ready$ should be cold + const ready$ = source$.pipe(withLatestReady(watch$)); + expect(connected).toBe(false); + + ready$.subscribe(); + + expect(connected).toBe(true); + }); + + it("suppresses source emissions until its watch emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + const results: [number, string][] = []; + ready$.subscribe((n) => results.push(n)); + + // precondition: no emissions + source$.next(1); + expect(results).toEqual([]); + + watch$.next("watch"); + + expect(results).toEqual([[1, "watch"]]); + }); + + it("emits the last source emission when its watch emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + const results: [number, string][] = []; + ready$.subscribe((n) => results.push(n)); + + // precondition: no emissions + source$.next(1); + expect(results).toEqual([]); + + source$.next(2); + watch$.next("watch"); + + expect(results).toEqual([[2, "watch"]]); + }); + + it("emits all source emissions after its watch emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + const results: [number, string][] = []; + ready$.subscribe((n) => results.push(n)); + + watch$.next("watch"); + source$.next(1); + source$.next(2); + + expect(results).toEqual([ + [1, "watch"], + [2, "watch"], + ]); + }); + + it("appends the latest watch emission", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + const results: [number, string][] = []; + ready$.subscribe((n) => results.push(n)); + + watch$.next("ignored"); + watch$.next("watch"); + source$.next(1); + watch$.next("ignored"); + watch$.next("watch"); + source$.next(2); + + expect(results).toEqual([ + [1, "watch"], + [2, "watch"], + ]); + }); + + it("completes when its source completes", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + let completed = false; + ready$.subscribe({ complete: () => (completed = true) }); + + source$.complete(); + + expect(completed).toBeTruthy(); + }); + + it("errors when its source errors", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + const expected = { some: "error" }; + let error = null; + ready$.subscribe({ error: (e: unknown) => (error = e) }); + + source$.error(expected); + + expect(error).toEqual(expected); + }); + + it("errors when its watch errors", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + const expected = { some: "error" }; + let error = null; + ready$.subscribe({ error: (e: unknown) => (error = e) }); + + watch$.error(expected); + + expect(error).toEqual(expected); + }); + + it("errors when its watch completes before emitting", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const ready$ = source$.pipe(withLatestReady(watch$)); + let error = null; + ready$.subscribe({ error: (e: unknown) => (error = e) }); + + watch$.complete(); + + expect(error).toBeInstanceOf(EmptyError); + }); + }); + + describe("on", () => { + it("connects when subscribed", () => { + const watch$ = new Subject(); + let connected = false; + const source$ = new Subject().pipe(tap({ subscribe: () => (connected = true) })); + + // precondition: on$ should be cold + const on$ = source$.pipe(on(watch$)); + expect(connected).toBeFalsy(); + + on$.subscribe(); + + expect(connected).toBeTruthy(); + }); + + it("suppresses source emissions until `on` emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(on(watch$)).subscribe((n) => results.push(n)); + + // precondition: on$ should be cold + source$.next(1); + expect(results).toEqual([]); + + watch$.next(); + + expect(results).toEqual([1]); + }); + + it("repeats source emissions when `on` emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(on(watch$)).subscribe((n) => results.push(n)); + source$.next(1); + + watch$.next(); + watch$.next(); + + expect(results).toEqual([1, 1]); + }); + + it("updates source emissions when `on` emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(on(watch$)).subscribe((n) => results.push(n)); + + source$.next(1); + watch$.next(); + source$.next(2); + watch$.next(); + + expect(results).toEqual([1, 2]); + }); + + it("emits a value when `on` emits before the source is ready", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(on(watch$)).subscribe((n) => results.push(n)); + + watch$.next(); + source$.next(1); + + expect(results).toEqual([1]); + }); + + it("ignores repeated `on` emissions before the source is ready", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(on(watch$)).subscribe((n) => results.push(n)); + + watch$.next(); + watch$.next(); + source$.next(1); + + expect(results).toEqual([1]); + }); + + it("emits only the latest source emission when `on` emits", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const results: number[] = []; + source$.pipe(on(watch$)).subscribe((n) => results.push(n)); + source$.next(1); + + watch$.next(); + + source$.next(2); + source$.next(3); + watch$.next(); + + expect(results).toEqual([1, 3]); + }); + + it("completes when its source completes", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + let complete: boolean = false; + source$.pipe(on(watch$)).subscribe({ complete: () => (complete = true) }); + + source$.complete(); + + expect(complete).toBeTruthy(); + }); + + it("completes when its watch completes", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + let complete: boolean = false; + source$.pipe(on(watch$)).subscribe({ complete: () => (complete = true) }); + + watch$.complete(); + + expect(complete).toBeTruthy(); + }); + + it("errors when its source errors", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const expected = { some: "error" }; + let error = null; + source$.pipe(on(watch$)).subscribe({ error: (e: unknown) => (error = e) }); + + source$.error(expected); + + expect(error).toEqual(expected); + }); + + it("errors when its watch errors", () => { + const watch$ = new Subject(); + const source$ = new Subject(); + const expected = { some: "error" }; + let error = null; + source$.pipe(on(watch$)).subscribe({ error: (e: unknown) => (error = e) }); + + watch$.error(expected); + + expect(error).toEqual(expected); + }); + }); + + describe("pin", () => { + it("emits the first value", async () => { + const input = new Subject(); + const result: unknown[] = []; + + input.pipe(pin()).subscribe((v) => result.push(v)); + input.next(1); + + expect(result).toEqual([1]); + }); + + it("filters repeated emissions", async () => { + const input = new Subject(); + const result: unknown[] = []; + + input.pipe(pin({ distinct: (p, c) => p == c })).subscribe((v) => result.push(v)); + input.next(1); + input.next(1); + + expect(result).toEqual([1]); + }); + + it("errors if multiple emissions occur", async () => { + const input = new Subject(); + let error: any = null!; + + input.pipe(pin()).subscribe({ + error: (e: unknown) => { + error = e; + }, + }); + input.next(1); + input.next(1); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatch(/^unknown/); + }); + + it("names the pinned observables if multiple emissions occur", async () => { + const input = new Subject(); + let error: any = null!; + + input.pipe(pin({ name: () => "example" })).subscribe({ + error: (e: unknown) => { + error = e; + }, + }); + input.next(1); + input.next(1); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatch(/^example/); + }); + + it("errors if indistinct emissions occur", async () => { + const input = new Subject(); + let error: any = null!; + + input + .pipe(pin({ distinct: (p, c) => p == c })) + .subscribe({ error: (e: unknown) => (error = e) }); + input.next(1); + input.next(2); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatch(/^unknown/); + }); + }); + + describe("memoizedMap", () => { + it("maps a value", () => { + const source$ = new Subject(); + const result$ = new BehaviorSubject({}); + const expectedResult = {}; + source$.pipe(memoizedMap(() => expectedResult)).subscribe(result$); + + source$.next("foo"); + + expect(result$.value).toEqual(expectedResult); + }); + + it("caches a mapped result", () => { + const source$ = new Subject(); + const result$ = new BehaviorSubject({}); + const map = jest.fn(() => ({})); + source$.pipe(memoizedMap(map)).subscribe(result$); + + source$.next("foo"); + source$.next("foo"); + + expect(map).toHaveBeenCalledTimes(1); + }); + + it("caches the last mapped result", () => { + const source$ = new Subject(); + const result$ = new BehaviorSubject({}); + const map = jest.fn(() => ({})); + source$.pipe(memoizedMap(map)).subscribe(result$); + + source$.next("foo"); + source$.next("foo"); + source$.next("bar"); + source$.next("foo"); + + expect(map).toHaveBeenCalledTimes(3); + }); + + it("caches multiple mapped results", () => { + const source$ = new Subject(); + const result$ = new BehaviorSubject({}); + const map = jest.fn(() => ({})); + source$.pipe(memoizedMap(map, { size: 2 })).subscribe(result$); + + source$.next("foo"); + source$.next("bar"); + source$.next("foo"); + source$.next("bar"); + + expect(map).toHaveBeenCalledTimes(2); + }); + + it("caches a result by key", () => { + const source$ = new Subject<{ key: string }>(); + const result$ = new BehaviorSubject({}); + const map = jest.fn(() => ({})); + source$.pipe(memoizedMap(map, { key: (s) => s.key })).subscribe(result$); + + // the messages are not equal; the keys are + source$.next({ key: "foo" }); + source$.next({ key: "foo" }); + source$.next({ key: "bar" }); + source$.next({ key: "bar" }); + + expect(map).toHaveBeenCalledTimes(2); + }); + }); + + it("errors", () => { + const source$ = new Subject(); + let error: unknown = null; + source$.pipe(memoizedMap(() => {})).subscribe({ error: (e: unknown) => (error = e) }); + const expectedError = {}; + + source$.error(expectedError); + + expect(error).toEqual(expectedError); + }); + + it("completes", () => { + const source$ = new Subject(); + let completed = false; + source$.pipe(memoizedMap(() => {})).subscribe({ complete: () => (completed = true) }); + + source$.complete(); + + expect(completed).toEqual(true); }); }); diff --git a/libs/common/src/tools/rx.ts b/libs/common/src/tools/rx.ts index ea397135581..4c9e547d151 100644 --- a/libs/common/src/tools/rx.ts +++ b/libs/common/src/tools/rx.ts @@ -20,8 +20,13 @@ import { startWith, pairwise, MonoTypeOperatorFunction, + Cons, + scan, + filter, } from "rxjs"; +import { ObservableTuple } from "./rx.rxjs"; + /** Returns its input. */ function identity(value: any): any { return value; @@ -164,26 +169,30 @@ export function ready(watch$: Observable | Observable[]) { ); } -export function withLatestReady( - watch$: Observable, -): OperatorFunction { +export function withLatestReady( + ...watches$: [...ObservableTuple] +): OperatorFunction> { return connect((source$) => { // these subscriptions are safe because `source$` connects only after there // is an external subscriber. const source = new ReplaySubject(1); source$.subscribe(source); - const watch = new ReplaySubject(1); - watch$.subscribe(watch); + + const watches = watches$.map((w) => { + const watch$ = new ReplaySubject(1); + w.subscribe(watch$); + return watch$; + }) as [...ObservableTuple]; // `concat` is subscribed immediately after it's returned, at which point - // `zip` blocks until all items in `watching$` are ready. If that occurs + // `zip` blocks until all items in `watches` are ready. If that occurs // after `source$` is hot, then the replay subject sends the last-captured - // emission through immediately. Otherwise, `ready` waits for the next - // emission - return concat(zip(watch).pipe(first(), ignoreElements()), source).pipe( - withLatestFrom(watch), + // emission through immediately. Otherwise, `withLatestFrom` waits for the + // next emission + return concat(zip(watches).pipe(first(), ignoreElements()), source).pipe( + withLatestFrom(...watches), takeUntil(anyComplete(source)), - ); + ) as Observable>; }); } @@ -238,3 +247,54 @@ export function pin(options?: { }), ); } + +/** maps a value to a result and keeps a cache of the mapping + * @param mapResult - maps the stream to a result; this function must return + * a value. It must not return null or undefined. + * @param options.size - the number of entries in the cache + * @param options.key - maps the source to a cache key + * @remarks this method is useful for optimization of expensive + * `mapResult` calls. It's also useful when an interned reference type + * is needed. + */ +export function memoizedMap>( + mapResult: (source: Source) => Result, + options?: { size?: number; key?: (source: Source) => unknown }, +): OperatorFunction { + return pipe( + // scan's accumulator contains the cache + scan( + ([cache], source) => { + const key: unknown = options?.key?.(source) ?? source; + + // cache hit? + let result = cache?.get(key); + if (result) { + return [cache, result] as const; + } + + // cache miss + result = mapResult(source); + cache?.set(key, result); + + // trim cache + const overage = cache.size - (options?.size ?? 1); + if (overage > 0) { + Array.from(cache?.keys() ?? []) + .slice(0, overage) + .forEach((k) => cache?.delete(k)); + } + + return [cache, result] as const; + }, + // FIXME: upgrade to a least-recently-used cache + [new Map(), null] as [Map, Source | null], + ), + + // encapsulate cache + map(([, result]) => result), + + // preserve `NonNullable` constraint on `Result` + filter((result): result is Result => !!result), + ); +} diff --git a/libs/common/src/tools/state/user-state-subject-dependency-provider.ts b/libs/common/src/tools/state/user-state-subject-dependency-provider.ts index 2763aac4830..89b6d7e7270 100644 --- a/libs/common/src/tools/state/user-state-subject-dependency-provider.ts +++ b/libs/common/src/tools/state/user-state-subject-dependency-provider.ts @@ -15,4 +15,9 @@ export abstract class UserStateSubjectDependencyProvider { // FIXME: remove `log` and inject the system provider into the USS instead /** Provides semantic logging */ abstract log: (_context: Jsonify) => SemanticLogger; + + /** Get the system time as a number of seconds since the unix epoch + * @remarks this can be turned into a date using `new Date(provider.now())` + */ + abstract now: () => number; } diff --git a/libs/common/src/tools/state/user-state-subject.spec.ts b/libs/common/src/tools/state/user-state-subject.spec.ts index 20acff17877..a6d452d37fd 100644 --- a/libs/common/src/tools/state/user-state-subject.spec.ts +++ b/libs/common/src/tools/state/user-state-subject.spec.ts @@ -90,6 +90,7 @@ const SomeProvider = { } as LegacyEncryptorProvider, state: SomeStateProvider, log: disabledSemanticLoggerProvider, + now: () => 100, }; function fooMaxLength(maxLength: number): StateConstraints { diff --git a/libs/common/src/tools/state/user-state-subject.ts b/libs/common/src/tools/state/user-state-subject.ts index dd88ec2fb20..118b0069c84 100644 --- a/libs/common/src/tools/state/user-state-subject.ts +++ b/libs/common/src/tools/state/user-state-subject.ts @@ -477,7 +477,12 @@ export class UserStateSubject< * @returns the subscription */ subscribe(observer?: Partial> | ((value: State) => void) | null): Subscription { - return this.output.pipe(map((wc) => wc.state)).subscribe(observer); + return this.output + .pipe( + map((wc) => wc.state), + distinctUntilChanged(), + ) + .subscribe(observer); } // using subjects to ensure the right semantics are followed; diff --git a/libs/common/src/tools/types.ts b/libs/common/src/tools/types.ts index 83f451351c2..6123b1f1dea 100644 --- a/libs/common/src/tools/types.ts +++ b/libs/common/src/tools/types.ts @@ -2,6 +2,11 @@ import { Simplify } from "type-fest"; import { IntegrationId } from "./integration"; +/** When this is a string, it contains the i18n key. When it is an object, the `literal` member + * contains text that should not be translated. + */ +export type I18nKeyOrLiteral = string | { literal: string }; + /** Constraints that are shared by all primitive field types */ type PrimitiveConstraint = { /** `true` indicates the field is required; otherwise the field is optional */ diff --git a/libs/common/src/tools/util.ts b/libs/common/src/tools/util.ts index 9a3a14c1c83..0fcb5a972af 100644 --- a/libs/common/src/tools/util.ts +++ b/libs/common/src/tools/util.ts @@ -1,3 +1,5 @@ +import { I18nKeyOrLiteral } from "./types"; + /** Recursively freeze an object's own keys * @param value the value to freeze * @returns `value` @@ -10,10 +12,22 @@ export function deepFreeze(value: T): Readonly { for (const key of keys) { const own = value[key]; - if ((own && typeof own === "object") || typeof own === "function") { + if (own && typeof own === "object") { deepFreeze(own); } } return Object.freeze(value); } + +/** Type guard that returns `true` when the value is an i18n key. */ +export function isI18nKey(value: I18nKeyOrLiteral): value is string { + return typeof value === "string"; +} + +/** Type guard that returns `true` when the value requires no translation. + * @remarks the literal value can be accessed using the `.literal` property. + */ +export function isLiteral(value: I18nKeyOrLiteral): value is { literal: string } { + return typeof value === "object" && "literal" in value; +} diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts index 0512b56e20d..956cc611c2e 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts +++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts @@ -57,7 +57,7 @@ import { ToastService, } from "@bitwarden/components"; import { GeneratorServicesModule } from "@bitwarden/generator-components"; -import { CredentialGeneratorService, GenerateRequest, Generators } from "@bitwarden/generator-core"; +import { CredentialGeneratorService, GenerateRequest, Type } from "@bitwarden/generator-core"; import { ExportedVault, VaultExportServiceAbstraction } from "@bitwarden/vault-export-core"; import { EncryptedExportType } from "../enums/encrypted-export-type.enum"; @@ -248,7 +248,7 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { }), ); this.generatorService - .generate$(Generators.password, { on$: this.onGenerate$, account$ }) + .generate$({ on$: this.onGenerate$, account$ }) .pipe(takeUntil(this.destroy$)) .subscribe((generated) => { this.exportForm.patchValue({ @@ -379,7 +379,7 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit { } generatePassword = async () => { - this.onGenerate$.next({ source: "export" }); + this.onGenerate$.next({ source: "export", type: Type.password }); }; submit = async () => { diff --git a/libs/tools/generator/components/src/catchall-settings.component.ts b/libs/tools/generator/components/src/catchall-settings.component.ts index 3bf586c5a29..a836b26f98b 100644 --- a/libs/tools/generator/components/src/catchall-settings.component.ts +++ b/libs/tools/generator/components/src/catchall-settings.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component, EventEmitter, @@ -17,7 +15,7 @@ import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { CatchallGenerationOptions, CredentialGeneratorService, - Generators, + BuiltIn, } from "@bitwarden/generator-core"; /** Options group for catchall emails */ @@ -28,7 +26,6 @@ import { }) export class CatchallSettingsComponent implements OnInit, OnDestroy, OnChanges { /** Instantiates the component - * @param accountService queries user availability * @param generatorService settings and policy logic * @param formBuilder reactive form controls */ @@ -37,24 +34,26 @@ export class CatchallSettingsComponent implements OnInit, OnDestroy, OnChanges { private generatorService: CredentialGeneratorService, ) {} - /** Binds the component to a specific user's settings. + /** Binds the component to a specific user's settings.\ + * @remarks this is initialized to null but since it's a required input it'll + * never have that value in practice. */ @Input({ required: true }) - account: Account; + account!: Account; private account$ = new ReplaySubject(1); /** Emits settings updates and completes if the settings become unavailable. * @remarks this does not emit the initial settings. If you would like * to receive live settings updates including the initial update, - * use `CredentialGeneratorService.settings$(...)` instead. + * use `CredentialGeneratorService.settings(...)` instead. */ @Output() readonly onUpdated = new EventEmitter(); /** The template's control bindings */ protected settings = this.formBuilder.group({ - catchallDomain: [Generators.catchall.settings.initial.catchallDomain], + catchallDomain: [""], }); async ngOnChanges(changes: SimpleChanges) { @@ -64,7 +63,7 @@ export class CatchallSettingsComponent implements OnInit, OnDestroy, OnChanges { } async ngOnInit() { - const settings = await this.generatorService.settings(Generators.catchall, { + const settings = await this.generatorService.settings(BuiltIn.catchall, { account$: this.account$, }); @@ -79,7 +78,7 @@ export class CatchallSettingsComponent implements OnInit, OnDestroy, OnChanges { this.saveSettings .pipe( withLatestFrom(this.settings.valueChanges), - map(([, settings]) => settings), + map(([, settings]) => settings as CatchallGenerationOptions), takeUntil(this.destroyed$), ) .subscribe(settings); diff --git a/libs/tools/generator/components/src/credential-generator-history.component.ts b/libs/tools/generator/components/src/credential-generator-history.component.ts index e7dadb3da71..76dfbaea867 100644 --- a/libs/tools/generator/components/src/credential-generator-history.component.ts +++ b/libs/tools/generator/components/src/credential-generator-history.component.ts @@ -6,6 +6,7 @@ import { BehaviorSubject, ReplaySubject, Subject, map, switchMap, takeUntil, tap import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { SemanticLogger, @@ -19,10 +20,11 @@ import { ItemModule, NoItemsModule, } from "@bitwarden/components"; -import { CredentialGeneratorService } from "@bitwarden/generator-core"; +import { AlgorithmsByType, CredentialGeneratorService } from "@bitwarden/generator-core"; import { GeneratedCredential, GeneratorHistoryService } from "@bitwarden/generator-history"; import { GeneratorModule } from "./generator.module"; +import { translate } from "./util"; @Component({ standalone: true, @@ -45,6 +47,7 @@ export class CredentialGeneratorHistoryComponent implements OnChanges, OnInit, O constructor( private generatorService: CredentialGeneratorService, private history: GeneratorHistoryService, + private i18nService: I18nService, private logService: LogService, ) {} @@ -94,13 +97,19 @@ export class CredentialGeneratorHistoryComponent implements OnChanges, OnInit, O } protected getCopyText(credential: GeneratedCredential) { - const info = this.generatorService.algorithm(credential.category); - return info.copy; + // there isn't a way way to look up category metadata so + // bodge it by looking up algorithm metadata + const [id] = AlgorithmsByType[credential.category]; + const info = this.generatorService.algorithm(id); + return translate(info.i18nKeys.copyCredential, this.i18nService); } protected getGeneratedValueText(credential: GeneratedCredential) { - const info = this.generatorService.algorithm(credential.category); - return info.credentialType; + // there isn't a way way to look up category metadata so + // bodge it by looking up algorithm metadata + const [id] = AlgorithmsByType[credential.category]; + const info = this.generatorService.algorithm(id); + return translate(info.i18nKeys.credentialType, this.i18nService); } ngOnDestroy() { diff --git a/libs/tools/generator/components/src/credential-generator.component.html b/libs/tools/generator/components/src/credential-generator.component.html index 24146968456..559554b7ddb 100644 --- a/libs/tools/generator/components/src/credential-generator.component.html +++ b/libs/tools/generator/components/src/credential-generator.component.html @@ -42,13 +42,13 @@ @@ -84,7 +84,7 @@ @@ -94,12 +94,12 @@ [forwarder]="forwarderId$ | async" /> diff --git a/libs/tools/generator/components/src/credential-generator.component.ts b/libs/tools/generator/components/src/credential-generator.component.ts index 0b48b4cf0f7..78b803392df 100644 --- a/libs/tools/generator/components/src/credential-generator.component.ts +++ b/libs/tools/generator/components/src/credential-generator.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { LiveAnnouncer } from "@angular/cdk/a11y"; import { Component, @@ -24,15 +22,15 @@ import { map, ReplaySubject, Subject, - switchMap, takeUntil, + tap, withLatestFrom, } from "rxjs"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { IntegrationId } from "@bitwarden/common/tools/integration"; +import { VendorId } from "@bitwarden/common/tools/extension"; import { SemanticLogger, disabledSemanticLoggerProvider, @@ -41,23 +39,25 @@ import { import { UserId } from "@bitwarden/common/types/guid"; import { ToastService, Option } from "@bitwarden/components"; import { - AlgorithmInfo, - CredentialAlgorithm, - CredentialCategory, + CredentialType, CredentialGeneratorService, GenerateRequest, GeneratedCredential, - Generators, - getForwarderConfiguration, - isEmailAlgorithm, - isForwarderIntegration, - isPasswordAlgorithm, + isForwarderExtensionId, isSameAlgorithm, + isEmailAlgorithm, isUsernameAlgorithm, - toCredentialGeneratorConfiguration, + isPasswordAlgorithm, + CredentialAlgorithm, + AlgorithmMetadata, + Algorithm, + AlgorithmsByType, + Type, } from "@bitwarden/generator-core"; import { GeneratorHistoryService } from "@bitwarden/generator-history"; +import { translate } from "./util"; + // constants used to identify navigation selections that are not // generator algorithms const IDENTIFIER = "identifier"; @@ -84,11 +84,14 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro private ariaLive: LiveAnnouncer, ) {} + /** exports algorithm symbols to the template */ + protected readonly Algorithm = Algorithm; + /** Binds the component to a specific user's settings. When this input is not provided, * the form binds to the active user */ @Input() - account: Account | null; + account: Account | null = null; /** Send structured debug logs from the credential generator component * to the debugger console. @@ -127,7 +130,7 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro @Output() readonly onGenerated = new EventEmitter(); - protected root$ = new BehaviorSubject<{ nav: string }>({ + protected root$ = new BehaviorSubject<{ nav: string | null }>({ nav: null, }); @@ -141,11 +144,11 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro } protected username = this.formBuilder.group({ - nav: [null as string], + nav: [null as string | null], }); protected forwarder = this.formBuilder.group({ - nav: [null as string], + nav: [null as string | null], }); async ngOnInit() { @@ -154,33 +157,52 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro }); if (!this.account) { - this.account = await firstValueFrom(this.accountService.activeAccount$); - this.log.info( - { userId: this.account.id }, - "account not specified; using active account settings", - ); - this.account$.next(this.account); + const account = await firstValueFrom(this.accountService.activeAccount$); + if (!account) { + this.log.panic("active account cannot be `null`."); + } + + this.log.info({ userId: account.id }, "account not specified; using active account settings"); + this.account$.next(account); } - this.generatorService - .algorithms$(["email", "username"], { account$: this.account$ }) + combineLatest([ + this.generatorService.algorithms$("email", { account$: this.account$ }), + this.generatorService.algorithms$("username", { account$: this.account$ }), + ]) .pipe( + map((algorithms) => algorithms.flat()), map((algorithms) => { - const usernames = algorithms.filter((a) => !isForwarderIntegration(a.id)); + // construct options for username and email algorithms; replace forwarder + // entry with a virtual entry for drill-down + const usernames = algorithms.filter((a) => !isForwarderExtensionId(a.id)); + usernames.sort((a, b) => a.weight - b.weight); const usernameOptions = this.toOptions(usernames); - usernameOptions.push({ value: FORWARDER, label: this.i18nService.t("forwardedEmail") }); + usernameOptions.splice(-1, 0, { + value: FORWARDER, + label: this.i18nService.t("forwardedEmail"), + }); - const forwarders = algorithms.filter((a) => isForwarderIntegration(a.id)); + // construct options for forwarder algorithms; they get their own selection box + const forwarders = algorithms.filter((a) => isForwarderExtensionId(a.id)); + forwarders.sort((a, b) => a.weight - b.weight); const forwarderOptions = this.toOptions(forwarders); forwarderOptions.unshift({ value: NONE_SELECTED, label: this.i18nService.t("select") }); return [usernameOptions, forwarderOptions] as const; }), + tap((algorithms) => + this.log.debug({ algorithms: algorithms as object }, "algorithms loaded"), + ), takeUntil(this.destroyed), ) .subscribe(([usernames, forwarders]) => { - this.usernameOptions$.next(usernames); - this.forwarderOptions$.next(forwarders); + // update subjects within the angular zone so that the + // template bindings refresh immediately + this.zone.run(() => { + this.usernameOptions$.next(usernames); + this.forwarderOptions$.next(forwarders); + }); }); this.generatorService @@ -195,9 +217,15 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro ) .subscribe(this.rootOptions$); - this.algorithm$ + this.maybeAlgorithm$ .pipe( - map((a) => a?.description), + map((a) => { + if (a?.i18nKeys?.description) { + return translate(a.i18nKeys.description, this.i18nService); + } else { + return ""; + } + }), takeUntil(this.destroyed), ) .subscribe((hint) => { @@ -208,9 +236,9 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro }); }); - this.algorithm$ + this.maybeAlgorithm$ .pipe( - map((a) => a?.category), + map((a) => a?.type), distinctUntilChanged(), takeUntil(this.destroyed), ) @@ -223,10 +251,12 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro }); // wire up the generator - this.algorithm$ + this.generatorService + .generate$({ + on$: this.generate$, + account$: this.account$, + }) .pipe( - filter((algorithm) => !!algorithm), - switchMap((algorithm) => this.typeToGenerator$(algorithm.id)), catchError((error: unknown, generator) => { if (typeof error === "string") { this.toastService.showToast({ @@ -241,11 +271,14 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro // continue with origin stream return generator; }), - withLatestFrom(this.account$, this.algorithm$), + withLatestFrom(this.account$, this.maybeAlgorithm$), takeUntil(this.destroyed), ) .subscribe(([generated, account, algorithm]) => { - this.log.debug({ source: generated.source }, "credential generated"); + this.log.debug( + { source: generated.source ?? null, algorithm: algorithm?.id ?? null }, + "credential generated", + ); this.generatorHistoryService .track(account.id, generated.credential, generated.category, generated.generationDate) @@ -256,8 +289,8 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro // update subjects within the angular zone so that the // template bindings refresh immediately this.zone.run(() => { - if (generated.source === this.USER_REQUEST) { - this.announce(algorithm.onGeneratedMessage); + if (algorithm && generated.source === this.USER_REQUEST) { + this.announce(translate(algorithm.i18nKeys.credentialGenerated, this.i18nService)); } this.generatedCredential$.next(generated); @@ -274,36 +307,46 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro this.root$ .pipe( - map( - (root): CascadeValue => - root.nav === IDENTIFIER - ? { nav: root.nav } - : { nav: root.nav, algorithm: JSON.parse(root.nav) }, - ), + map((root): CascadeValue => { + if (root.nav === IDENTIFIER) { + return { nav: root.nav }; + } else if (root.nav) { + return { nav: root.nav, algorithm: JSON.parse(root.nav) }; + } else { + return { nav: IDENTIFIER }; + } + }), takeUntil(this.destroyed), ) .subscribe(activeRoot$); this.username.valueChanges .pipe( - map( - (username): CascadeValue => - username.nav === FORWARDER - ? { nav: username.nav } - : { nav: username.nav, algorithm: JSON.parse(username.nav) }, - ), + map((username): CascadeValue => { + if (username.nav === FORWARDER) { + return { nav: username.nav }; + } else if (username.nav) { + return { nav: username.nav, algorithm: JSON.parse(username.nav) }; + } else { + const [algorithm] = AlgorithmsByType[Type.username]; + return { nav: JSON.stringify(algorithm), algorithm }; + } + }), takeUntil(this.destroyed), ) .subscribe(activeIdentifier$); this.forwarder.valueChanges .pipe( - map( - (forwarder): CascadeValue => - forwarder.nav === NONE_SELECTED - ? { nav: forwarder.nav } - : { nav: forwarder.nav, algorithm: JSON.parse(forwarder.nav) }, - ), + map((forwarder): CascadeValue => { + if (forwarder.nav === NONE_SELECTED) { + return { nav: forwarder.nav }; + } else if (forwarder.nav) { + return { nav: forwarder.nav, algorithm: JSON.parse(forwarder.nav) }; + } else { + return { nav: NONE_SELECTED }; + } + }), takeUntil(this.destroyed), ) .subscribe(activeForwarder$); @@ -314,7 +357,7 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro map(([root, username, forwarder]) => { const showForwarder = !root.algorithm && !username.algorithm; const forwarderId = - showForwarder && isForwarderIntegration(forwarder.algorithm) + showForwarder && forwarder.algorithm && isForwarderExtensionId(forwarder.algorithm) ? forwarder.algorithm.forwarder : null; return [showForwarder, forwarderId] as const; @@ -344,47 +387,51 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro return null; } }), - distinctUntilChanged((prev, next) => isSameAlgorithm(prev?.id, next?.id)), + distinctUntilChanged((prev, next) => { + if (prev === null || next === null) { + return false; + } else { + return isSameAlgorithm(prev.id, next.id); + } + }), takeUntil(this.destroyed), ) .subscribe((algorithm) => { - this.log.debug(algorithm, "algorithm selected"); + this.log.debug({ algorithm: algorithm?.id ?? null }, "algorithm selected"); // update subjects within the angular zone so that the // template bindings refresh immediately this.zone.run(() => { - this.algorithm$.next(algorithm); + this.maybeAlgorithm$.next(algorithm); }); }); // assume the last-selected generator algorithm is the user's preferred one const preferences = await this.generatorService.preferences({ account$: this.account$ }); this.algorithm$ - .pipe( - filter((algorithm) => !!algorithm), - withLatestFrom(preferences), - takeUntil(this.destroyed), - ) + .pipe(withLatestFrom(preferences), takeUntil(this.destroyed)) .subscribe(([algorithm, preference]) => { - function setPreference(category: CredentialCategory, log: SemanticLogger) { - const p = preference[category]; + function setPreference(type: CredentialType) { + const p = preference[type]; p.algorithm = algorithm.id; p.updated = new Date(); - - log.info({ algorithm, category }, "algorithm preferences updated"); } // `is*Algorithm` decides `algorithm`'s type, which flows into `setPreference` if (isEmailAlgorithm(algorithm.id)) { - setPreference("email", this.log); + setPreference("email"); } else if (isUsernameAlgorithm(algorithm.id)) { - setPreference("username", this.log); + setPreference("username"); } else if (isPasswordAlgorithm(algorithm.id)) { - setPreference("password", this.log); + setPreference("password"); } else { return; } + this.log.info( + { algorithm: algorithm.id, type: algorithm.type }, + "algorithm preferences updated", + ); preferences.next(preference); }); @@ -392,10 +439,12 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro preferences .pipe( map(({ email, username, password }) => { - const forwarderPref = isForwarderIntegration(email.algorithm) ? email : null; const usernamePref = email.updated > username.updated ? email : username; + const forwarderPref = isForwarderExtensionId(usernamePref.algorithm) + ? usernamePref + : null; - // inject drilldown flags + // inject drill-down flags const forwarderNav = !forwarderPref ? NONE_SELECTED : JSON.stringify(forwarderPref.algorithm); @@ -411,14 +460,14 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro selection: { nav: rootNav }, active: { nav: rootNav, - algorithm: rootNav === IDENTIFIER ? null : password.algorithm, + algorithm: rootNav === IDENTIFIER ? undefined : password.algorithm, } as CascadeValue, }, username: { selection: { nav: userNav }, active: { nav: userNav, - algorithm: forwarderPref ? null : usernamePref.algorithm, + algorithm: forwarderPref ? undefined : usernamePref.algorithm, }, }, forwarder: { @@ -435,6 +484,15 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro takeUntil(this.destroyed), ) .subscribe(({ root, username, forwarder }) => { + this.log.debug( + { + root: root.selection, + username: username.selection, + forwarder: forwarder.selection, + }, + "navigation updated", + ); + // update navigation; break subscription loop this.onRootChanged(root.selection); this.username.setValue(username.selection, { emitEvent: false }); @@ -448,16 +506,16 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro // automatically regenerate when the algorithm switches if the algorithm // allows it; otherwise set a placeholder - this.algorithm$.pipe(takeUntil(this.destroyed)).subscribe((a) => { + this.maybeAlgorithm$.pipe(takeUntil(this.destroyed)).subscribe((a) => { this.zone.run(() => { - if (!a || a.onlyOnRequest) { - this.log.debug("autogeneration disabled; clearing generated credential"); - this.generatedCredential$.next(null); - } else { + if (a?.capabilities?.autogenerate) { this.log.debug("autogeneration enabled"); this.generate("autogenerate").catch((e: unknown) => { this.log.error(e as object, "a failure occurred during autogeneration"); }); + } else { + this.log.debug("autogeneration disabled; clearing generated credential"); + this.generatedCredential$.next(undefined); } }); }); @@ -469,41 +527,6 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro this.ariaLive.announce(message).catch((e) => this.logService.error(e)); } - private typeToGenerator$(algorithm: CredentialAlgorithm) { - const dependencies = { - on$: this.generate$, - account$: this.account$, - }; - - this.log.debug({ algorithm }, "constructing generation stream"); - - switch (algorithm) { - case "catchall": - return this.generatorService.generate$(Generators.catchall, dependencies); - - case "subaddress": - return this.generatorService.generate$(Generators.subaddress, dependencies); - - case "username": - return this.generatorService.generate$(Generators.username, dependencies); - - case "password": - return this.generatorService.generate$(Generators.password, dependencies); - - case "passphrase": - return this.generatorService.generate$(Generators.passphrase, dependencies); - } - - if (isForwarderIntegration(algorithm)) { - const forwarder = getForwarderConfiguration(algorithm.forwarder); - const configuration = toCredentialGeneratorConfiguration(forwarder); - const generator = this.generatorService.generate$(configuration, dependencies); - return generator; - } - - this.log.panic({ algorithm }, `Invalid generator type: "${algorithm}"`); - } - /** Lists the top-level credential types supported by the component. * @remarks This is string-typed because angular doesn't support * structural equality for objects, which prevents `CredentialAlgorithm` @@ -519,15 +542,20 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro protected forwarderOptions$ = new BehaviorSubject[]>([]); /** Tracks the currently selected forwarder. */ - protected forwarderId$ = new BehaviorSubject(null); + protected forwarderId$ = new BehaviorSubject(null); /** Tracks forwarder control visibility */ protected showForwarder$ = new BehaviorSubject(false); /** tracks the currently selected credential type */ - protected algorithm$ = new ReplaySubject(1); + protected maybeAlgorithm$ = new ReplaySubject(1); - protected showAlgorithm$ = this.algorithm$.pipe( + /** tracks the last valid algorithm selection */ + protected algorithm$ = this.maybeAlgorithm$.pipe( + filter((algorithm): algorithm is AlgorithmMetadata => !!algorithm), + ); + + protected showAlgorithm$ = this.maybeAlgorithm$.pipe( combineLatestWith(this.showForwarder$), map(([algorithm, showForwarder]) => (showForwarder ? null : algorithm)), ); @@ -536,33 +564,32 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro * Emits the copy button aria-label respective of the selected credential type */ protected credentialTypeCopyLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ copy }) => copy), + map(({ i18nKeys: { copyCredential } }) => translate(copyCredential, this.i18nService)), ); /** * Emits the generate button aria-label respective of the selected credential type */ protected credentialTypeGenerateLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ generate }) => generate), + map(({ i18nKeys: { generateCredential } }) => translate(generateCredential, this.i18nService)), ); /** * Emits the copy credential toast respective of the selected credential type */ protected credentialTypeLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ credentialType }) => credentialType), + map(({ i18nKeys: { credentialType } }) => translate(credentialType, this.i18nService)), ); /** Emits hint key for the currently selected credential type */ - protected credentialTypeHint$ = new ReplaySubject(1); + protected credentialTypeHint$ = new ReplaySubject(1); /** tracks the currently selected credential category */ - protected category$ = new ReplaySubject(1); + protected category$ = new ReplaySubject(1); - private readonly generatedCredential$ = new BehaviorSubject(null); + private readonly generatedCredential$ = new BehaviorSubject( + undefined, + ); /** Emits the last generated value. */ protected readonly value$ = this.generatedCredential$.pipe( @@ -580,15 +607,20 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro * origin in the debugger. */ protected async generate(source: string) { - const request = { source, website: this.website }; + const algorithm = await firstValueFrom(this.algorithm$); + const request: GenerateRequest = { source, algorithm: algorithm.id }; + if (this.website) { + request.website = this.website; + } + this.log.debug(request, "generation requested"); this.generate$.next(request); } - private toOptions(algorithms: AlgorithmInfo[]) { + private toOptions(algorithms: AlgorithmMetadata[]) { const options: Option[] = algorithms.map((algorithm) => ({ value: JSON.stringify(algorithm.id), - label: algorithm.name, + label: translate(algorithm.i18nKeys.name, this.i18nService), })); return options; diff --git a/libs/tools/generator/components/src/forwarder-settings.component.ts b/libs/tools/generator/components/src/forwarder-settings.component.ts index 689cc7e258c..c961cd5bb7a 100644 --- a/libs/tools/generator/components/src/forwarder-settings.component.ts +++ b/libs/tools/generator/components/src/forwarder-settings.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component, EventEmitter, @@ -14,13 +12,11 @@ import { FormBuilder } from "@angular/forms"; import { map, ReplaySubject, skip, Subject, switchAll, takeUntil, withLatestFrom } from "rxjs"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; -import { IntegrationId } from "@bitwarden/common/tools/integration"; +import { VendorId } from "@bitwarden/common/tools/extension"; import { - CredentialGeneratorConfiguration, CredentialGeneratorService, - getForwarderConfiguration, - NoPolicy, - toCredentialGeneratorConfiguration, + ForwarderOptions, + GeneratorMetadata, } from "@bitwarden/generator-core"; const Controls = Object.freeze({ @@ -37,7 +33,6 @@ const Controls = Object.freeze({ }) export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component - * @param accountService queries user availability * @param generatorService settings and policy logic * @param formBuilder reactive form controls */ @@ -47,14 +42,16 @@ export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy ) {} /** Binds the component to a specific user's settings. + * @remarks this is initialized to null but since it's a required input it'll + * never have that value in practice. */ @Input({ required: true }) - account: Account; + account: Account = null!; protected account$ = new ReplaySubject(1); @Input({ required: true }) - forwarder: IntegrationId; + forwarder: VendorId = null!; /** Emits settings updates and completes if the settings become unavailable. * @remarks this does not emit the initial settings. If you would like @@ -71,24 +68,19 @@ export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy [Controls.baseUrl]: [""], }); - private forwarderId$ = new ReplaySubject(1); + private vendor = new ReplaySubject(1); async ngOnInit() { - const forwarder$ = new ReplaySubject>(1); - this.forwarderId$ + const forwarder$ = new ReplaySubject>(1); + this.vendor .pipe( - map((id) => getForwarderConfiguration(id)), - // type erasure necessary because the configuration properties are - // determined dynamically at runtime - // FIXME: this can be eliminated by unifying the forwarder settings types; - // see `ForwarderConfiguration<...>` for details. - map((forwarder) => toCredentialGeneratorConfiguration(forwarder)), + map((vendor) => this.generatorService.forwarder(vendor)), takeUntil(this.destroyed$), ) .subscribe((forwarder) => { - this.displayDomain = forwarder.request.includes("domain"); - this.displayToken = forwarder.request.includes("token"); - this.displayBaseUrl = forwarder.request.includes("baseUrl"); + this.displayDomain = forwarder.capabilities.fields.includes("domain"); + this.displayToken = forwarder.capabilities.fields.includes("token"); + this.displayBaseUrl = forwarder.capabilities.fields.includes("baseUrl"); forwarder$.next(forwarder); }); @@ -107,10 +99,10 @@ export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy forwarder$.pipe(takeUntil(this.destroyed$)).subscribe((forwarder) => { for (const name in Controls) { const control = this.settings.get(name); - if (forwarder.request.includes(name as any)) { - control.enable({ emitEvent: false }); + if (forwarder.capabilities.fields.includes(name)) { + control?.enable({ emitEvent: false }); } else { - control.disable({ emitEvent: false }); + control?.disable({ emitEvent: false }); } } }); @@ -128,7 +120,7 @@ export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy this.saveSettings .pipe(withLatestFrom(this.settings.valueChanges, settings$), takeUntil(this.destroyed$)) .subscribe(([, value, settings]) => { - settings.next(value); + settings.next(value as ForwarderOptions); }); } @@ -140,7 +132,7 @@ export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy async ngOnChanges(changes: SimpleChanges) { this.refresh$.complete(); if ("forwarder" in changes) { - this.forwarderId$.next(this.forwarder); + this.vendor.next(this.forwarder); } if ("account" in changes) { @@ -148,9 +140,9 @@ export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy } } - protected displayDomain: boolean; - protected displayToken: boolean; - protected displayBaseUrl: boolean; + protected displayDomain: boolean = false; + protected displayToken: boolean = false; + protected displayBaseUrl: boolean = false; private readonly refresh$ = new Subject(); diff --git a/libs/tools/generator/components/src/generator-services.module.ts b/libs/tools/generator/components/src/generator-services.module.ts index 214abbd0ac2..3a7b771a25d 100644 --- a/libs/tools/generator/components/src/generator-services.module.ts +++ b/libs/tools/generator/components/src/generator-services.module.ts @@ -7,19 +7,40 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateProvider } from "@bitwarden/common/platform/state"; import { KeyServiceLegacyEncryptorProvider } from "@bitwarden/common/tools/cryptography/key-service-legacy-encryptor-provider"; import { LegacyEncryptorProvider } from "@bitwarden/common/tools/cryptography/legacy-encryptor-provider"; -import { disabledSemanticLoggerProvider } from "@bitwarden/common/tools/log"; +import { Site } from "@bitwarden/common/tools/extension"; +import { ExtensionRegistry } from "@bitwarden/common/tools/extension/extension-registry.abstraction"; +import { ExtensionService } from "@bitwarden/common/tools/extension/extension.service"; +import { DefaultFields, DefaultSites, Extension } from "@bitwarden/common/tools/extension/metadata"; +import { RuntimeExtensionRegistry } from "@bitwarden/common/tools/extension/runtime-extension-registry"; +import { VendorExtensions, Vendors } from "@bitwarden/common/tools/extension/vendor"; +import { RestClient } from "@bitwarden/common/tools/integration/rpc"; +import { + LogProvider, + disabledSemanticLoggerProvider, + enableLogForTypes, +} from "@bitwarden/common/tools/log"; +import { SystemServiceProvider } from "@bitwarden/common/tools/providers"; import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider"; import { + BuiltIn, createRandomizer, CredentialGeneratorService, Randomizer, + providers, + DefaultCredentialGeneratorService, } from "@bitwarden/generator-core"; import { KeyService } from "@bitwarden/key-management"; export const RANDOMIZER = new SafeInjectionToken("Randomizer"); +const GENERATOR_SERVICE_PROVIDER = new SafeInjectionToken( + "CredentialGeneratorProviders", +); +const SYSTEM_SERVICE_PROVIDER = new SafeInjectionToken("SystemServices"); /** Shared module containing generator component dependencies */ @NgModule({ @@ -35,6 +56,116 @@ export const RANDOMIZER = new SafeInjectionToken("Randomizer"); useClass: KeyServiceLegacyEncryptorProvider, deps: [EncryptService, KeyService], }), + safeProvider({ + provide: ExtensionRegistry, + useFactory: () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + registry.registerSite(Extension[Site.forwarder]); + for (const vendor of Vendors) { + registry.registerVendor(vendor); + } + for (const extension of VendorExtensions) { + registry.registerExtension(extension); + } + registry.setPermission({ all: true }, "default"); + + return registry; + }, + deps: [], + }), + safeProvider({ + provide: SYSTEM_SERVICE_PROVIDER, + useFactory: ( + encryptor: LegacyEncryptorProvider, + state: StateProvider, + policy: PolicyService, + registry: ExtensionRegistry, + logger: LogService, + environment: PlatformUtilsService, + ) => { + let log: LogProvider; + if (environment.isDev()) { + log = enableLogForTypes(logger, []); + } else { + log = disabledSemanticLoggerProvider; + } + + const extension = new ExtensionService(registry, { + encryptor, + state, + log, + now: Date.now, + }); + + return { + policy, + extension, + log, + }; + }, + deps: [ + LegacyEncryptorProvider, + StateProvider, + PolicyService, + ExtensionRegistry, + LogService, + PlatformUtilsService, + ], + }), + safeProvider({ + provide: GENERATOR_SERVICE_PROVIDER, + useFactory: ( + system: SystemServiceProvider, + random: Randomizer, + encryptor: LegacyEncryptorProvider, + state: StateProvider, + i18n: I18nService, + api: ApiService, + ) => { + const userStateDeps = { + encryptor, + state, + log: system.log, + now: Date.now, + } satisfies UserStateSubjectDependencyProvider; + + const metadata = new providers.GeneratorMetadataProvider( + userStateDeps, + system, + Object.values(BuiltIn), + ); + const profile = new providers.GeneratorProfileProvider(userStateDeps, system.policy); + + const generator: providers.GeneratorDependencyProvider = { + randomizer: random, + client: new RestClient(api, i18n), + i18nService: i18n, + }; + + const userState: UserStateSubjectDependencyProvider = { + encryptor, + state, + log: system.log, + now: Date.now, + }; + + return { + userState, + generator, + profile, + metadata, + } satisfies providers.CredentialGeneratorProviders; + }, + deps: [ + SYSTEM_SERVICE_PROVIDER, + RANDOMIZER, + LegacyEncryptorProvider, + StateProvider, + I18nService, + ApiService, + ], + }), safeProvider({ provide: UserStateSubjectDependencyProvider, useFactory: (encryptor: LegacyEncryptorProvider, state: StateProvider) => @@ -42,19 +173,14 @@ export const RANDOMIZER = new SafeInjectionToken("Randomizer"); encryptor, state, log: disabledSemanticLoggerProvider, + now: Date.now, }), deps: [LegacyEncryptorProvider, StateProvider], }), safeProvider({ provide: CredentialGeneratorService, - useClass: CredentialGeneratorService, - deps: [ - RANDOMIZER, - PolicyService, - ApiService, - I18nService, - UserStateSubjectDependencyProvider, - ], + useClass: DefaultCredentialGeneratorService, + deps: [GENERATOR_SERVICE_PROVIDER, SYSTEM_SERVICE_PROVIDER], }), ], }) diff --git a/libs/tools/generator/components/src/passphrase-settings.component.ts b/libs/tools/generator/components/src/passphrase-settings.component.ts index 405914977c5..b3525251392 100644 --- a/libs/tools/generator/components/src/passphrase-settings.component.ts +++ b/libs/tools/generator/components/src/passphrase-settings.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { OnInit, @@ -12,14 +10,20 @@ import { OnChanges, } from "@angular/core"; import { FormBuilder } from "@angular/forms"; -import { skip, takeUntil, Subject, map, withLatestFrom, ReplaySubject } from "rxjs"; +import { skip, takeUntil, Subject, map, withLatestFrom, ReplaySubject, tap } from "rxjs"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { + SemanticLogger, + disabledSemanticLoggerProvider, + ifEnabledSemanticLoggerProvider, +} from "@bitwarden/common/tools/log"; import { - Generators, CredentialGeneratorService, PassphraseGenerationOptions, + BuiltIn, } from "@bitwarden/generator-core"; const Controls = Object.freeze({ @@ -45,12 +49,26 @@ export class PassphraseSettingsComponent implements OnInit, OnChanges, OnDestroy private formBuilder: FormBuilder, private generatorService: CredentialGeneratorService, private i18nService: I18nService, + private logService: LogService, ) {} + /** Send structured debug logs from the credential generator component + * to the debugger console. + * + * @warning this may reveal sensitive information in plaintext. + */ + @Input() + debug: boolean = false; + + // this `log` initializer is overridden in `ngOnInit` + private log: SemanticLogger = disabledSemanticLoggerProvider({}); + /** Binds the component to a specific user's settings. + * @remarks this is initialized to null but since it's a required input it'll + * never have that value in practice. */ @Input({ required: true }) - account: Account; + account: Account = null!; protected account$ = new ReplaySubject(1); @@ -70,53 +88,66 @@ export class PassphraseSettingsComponent implements OnInit, OnChanges, OnDestroy /** Emits settings updates and completes if the settings become unavailable. * @remarks this does not emit the initial settings. If you would like * to receive live settings updates including the initial update, - * use `CredentialGeneratorService.settings$(...)` instead. + * use {@link CredentialGeneratorService.settings} instead. */ @Output() readonly onUpdated = new EventEmitter(); protected settings = this.formBuilder.group({ - [Controls.numWords]: [Generators.passphrase.settings.initial.numWords], - [Controls.wordSeparator]: [Generators.passphrase.settings.initial.wordSeparator], - [Controls.capitalize]: [Generators.passphrase.settings.initial.capitalize], - [Controls.includeNumber]: [Generators.passphrase.settings.initial.includeNumber], + [Controls.numWords]: [0], + [Controls.wordSeparator]: [""], + [Controls.capitalize]: [false], + [Controls.includeNumber]: [false], }); async ngOnInit() { - const settings = await this.generatorService.settings(Generators.passphrase, { + this.log = ifEnabledSemanticLoggerProvider(this.debug, this.logService, { + type: "PassphraseSettingsComponent", + }); + + const settings = await this.generatorService.settings(BuiltIn.passphrase, { account$: this.account$, }); // skips reactive event emissions to break a subscription cycle settings.withConstraints$ - .pipe(takeUntil(this.destroyed$)) + .pipe( + tap((content) => this.log.debug(content, "passphrase settings loaded with constraints")), + takeUntil(this.destroyed$), + ) .subscribe(({ state, constraints }) => { this.settings.patchValue(state, { emitEvent: false }); let boundariesHint = this.i18nService.t( "spinboxBoundariesHint", - constraints.numWords.min?.toString(), - constraints.numWords.max?.toString(), + constraints.numWords?.min?.toString(), + constraints.numWords?.max?.toString(), ); - if (state.numWords <= (constraints.numWords.recommendation ?? 0)) { + if ((state.numWords ?? 0) <= (constraints.numWords?.recommendation ?? 0)) { boundariesHint += this.i18nService.t( "passphraseNumWordsRecommendationHint", - constraints.numWords.recommendation?.toString(), + constraints.numWords?.recommendation?.toString(), ); } this.numWordsBoundariesHint.next(boundariesHint); }); // the first emission is the current value; subsequent emissions are updates - settings.pipe(skip(1), takeUntil(this.destroyed$)).subscribe(this.onUpdated); + settings + .pipe( + skip(1), + tap((settings) => this.log.debug(settings, "passphrase settings onUpdate event")), + takeUntil(this.destroyed$), + ) + .subscribe(this.onUpdated); // explain policy & disable policy-overridden fields this.generatorService - .policy$(Generators.passphrase, { account$: this.account$ }) + .policy$(BuiltIn.passphrase, { account$: this.account$ }) .pipe(takeUntil(this.destroyed$)) .subscribe(({ constraints }) => { - this.wordSeparatorMaxLength = constraints.wordSeparator.maxLength; - this.policyInEffect = constraints.policyInEffect; + this.wordSeparatorMaxLength = constraints.wordSeparator?.maxLength ?? 0; + this.policyInEffect = constraints.policyInEffect ?? false; this.toggleEnabled(Controls.capitalize, !constraints.capitalize?.readonly); this.toggleEnabled(Controls.includeNumber, !constraints.includeNumber?.readonly); @@ -126,22 +157,25 @@ export class PassphraseSettingsComponent implements OnInit, OnChanges, OnDestroy this.saveSettings .pipe( withLatestFrom(this.settings.valueChanges), - map(([, settings]) => settings), + tap(([source, form]) => + this.log.debug({ source, form }, "save passphrase settings request"), + ), + map(([, settings]) => settings as PassphraseGenerationOptions), takeUntil(this.destroyed$), ) .subscribe(settings); } /** attribute binding for wordSeparator[maxlength] */ - protected wordSeparatorMaxLength: number; + protected wordSeparatorMaxLength: number = 0; private saveSettings = new Subject(); - save(site: string = "component api call") { - this.saveSettings.next(site); + save(source: string = "component api call") { + this.saveSettings.next(source); } /** display binding for enterprise policy notice */ - protected policyInEffect: boolean; + protected policyInEffect: boolean = false; private numWordsBoundariesHint = new ReplaySubject(1); @@ -150,9 +184,9 @@ export class PassphraseSettingsComponent implements OnInit, OnChanges, OnDestroy private toggleEnabled(setting: keyof typeof Controls, enabled: boolean) { if (enabled) { - this.settings.get(setting).enable({ emitEvent: false }); + this.settings.get(setting)?.enable({ emitEvent: false }); } else { - this.settings.get(setting).disable({ emitEvent: false }); + this.settings.get(setting)?.disable({ emitEvent: false }); } } diff --git a/libs/tools/generator/components/src/password-generator.component.html b/libs/tools/generator/components/src/password-generator.component.html index 41ed982a490..cc5bdba6062 100644 --- a/libs/tools/generator/components/src/password-generator.component.html +++ b/libs/tools/generator/components/src/password-generator.component.html @@ -3,6 +3,7 @@ class="tw-mb-4" [selected]="credentialType$ | async" (selectedChange)="onCredentialTypeChanged($event)" + *ngIf="showCredentialTypes$ | async" attr.aria-label="{{ 'type' | i18n }}" > @@ -38,14 +39,14 @@ (1); @@ -87,7 +94,11 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy async ngOnChanges(changes: SimpleChanges) { const account = changes?.account; - if (account?.previousValue?.id !== account?.currentValue?.id) { + if ( + account && + account.currentValue.id && + account.previousValue.id !== account.currentValue.id + ) { this.log.debug( { previousUserId: account?.previousValue?.id as UserId, @@ -95,15 +106,19 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy }, "account input change detected", ); - this.account$.next(this.account); + this.account$.next(account.currentValue.id); } } + @Input() + profile: GeneratorProfile = Profile.account; + /** Removes bottom margin, passed to downstream components */ - @Input({ transform: coerceBooleanProperty }) disableMargin = false; + @Input({ transform: coerceBooleanProperty }) + disableMargin = false; /** tracks the currently selected credential type */ - protected credentialType$ = new BehaviorSubject(null); + protected credentialType$ = new BehaviorSubject(null); /** Emits the last generated value. */ protected readonly value$ = new BehaviorSubject(""); @@ -119,9 +134,11 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy * origin in the debugger. */ protected async generate(source: string) { - this.log.debug({ source }, "generation requested"); + const algorithm = await firstValueFrom(this.algorithm$); + const request: GenerateRequest = { source, algorithm: algorithm.id, profile: this.profile }; - this.generate$.next({ source }); + this.log.debug(request, "generation requested"); + this.generate$.next(request); } /** Tracks changes to the selected credential type @@ -146,16 +163,17 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy async ngOnInit() { this.log = ifEnabledSemanticLoggerProvider(this.debug, this.logService, { - type: "UsernameGeneratorComponent", + type: "PasswordGeneratorComponent", }); if (!this.account) { - this.account = await firstValueFrom(this.accountService.activeAccount$); - this.log.info( - { userId: this.account.id }, - "account not specified; using active account settings", - ); - this.account$.next(this.account); + const account = await firstValueFrom(this.accountService.activeAccount$); + if (!account) { + this.log.panic("active account cannot be `null`."); + } + + this.log.info({ userId: account.id }, "account not specified; using active account settings"); + this.account$.next(account); } this.generatorService @@ -167,10 +185,9 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy .subscribe(this.passwordOptions$); // wire up the generator - this.algorithm$ + this.generatorService + .generate$({ on$: this.generate$, account$: this.account$ }) .pipe( - filter((algorithm) => !!algorithm), - switchMap((algorithm) => this.typeToGenerator$(algorithm.id)), catchError((error: unknown, generator) => { if (typeof error === "string") { this.toastService.showToast({ @@ -189,7 +206,7 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy takeUntil(this.destroyed), ) .subscribe(([generated, account, algorithm]) => { - this.log.debug({ source: generated.source }, "credential generated"); + this.log.debug({ source: generated.source ?? null }, "credential generated"); this.generatorHistoryService .track(account.id, generated.credential, generated.category, generated.generationDate) @@ -201,7 +218,7 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy // template bindings refresh immediately this.zone.run(() => { if (generated.source === this.USER_REQUEST) { - this.announce(algorithm.onGeneratedMessage); + this.announce(translate(algorithm.i18nKeys.credentialGenerated, this.i18nService)); } this.onGenerated.next(generated); @@ -219,10 +236,7 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy ) .subscribe(([algorithm, preference]) => { if (isPasswordAlgorithm(algorithm)) { - this.log.info( - { algorithm, category: CredentialCategories.password }, - "algorithm preferences updated", - ); + this.log.info({ algorithm, type: Type.password }, "algorithm preferences updated"); preference.password.algorithm = algorithm; preference.password.updated = new Date(); } else { @@ -236,11 +250,17 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy preferences .pipe( map(({ password }) => this.generatorService.algorithm(password.algorithm)), - distinctUntilChanged((prev, next) => isSameAlgorithm(prev?.id, next?.id)), + distinctUntilChanged((prev, next) => { + if (prev === null || next === null) { + return false; + } else { + return isSameAlgorithm(prev.id, next.id); + } + }), takeUntil(this.destroyed), ) .subscribe((algorithm) => { - this.log.debug(algorithm, "algorithm selected"); + this.log.debug({ algorithm: algorithm.id }, "algorithm selected"); // update navigation this.onCredentialTypeChanged(algorithm.id); @@ -248,22 +268,22 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy // update subjects within the angular zone so that the // template bindings refresh immediately this.zone.run(() => { - this.algorithm$.next(algorithm); - this.onAlgorithm.next(algorithm); + this.maybeAlgorithm$.next(algorithm); + this.onAlgorithm.next(toAlgorithmInfo(algorithm, this.i18nService)); }); }); // generate on load unless the generator prohibits it - this.algorithm$.pipe(takeUntil(this.destroyed)).subscribe((a) => { + this.maybeAlgorithm$.pipe(takeUntil(this.destroyed)).subscribe((a) => { this.zone.run(() => { - if (!a || a.onlyOnRequest) { - this.log.debug("autogeneration disabled; clearing generated credential"); - this.value$.next("-"); - } else { + if (a?.capabilities?.autogenerate) { this.log.debug("autogeneration enabled"); this.generate("autogenerate").catch((e: unknown) => { this.log.error(e as object, "a failure occurred during autogeneration"); }); + } else { + this.log.debug("autogeneration disabled; clearing generated credential"); + this.value$.next("-"); } }); }); @@ -275,59 +295,45 @@ export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy this.ariaLive.announce(message).catch((e) => this.logService.error(e)); } - private typeToGenerator$(algorithm: CredentialAlgorithm) { - const dependencies = { - on$: this.generate$, - account$: this.account$, - }; - - this.log.debug({ algorithm }, "constructing generation stream"); - - switch (algorithm) { - case "password": - return this.generatorService.generate$(Generators.password, dependencies); - - case "passphrase": - return this.generatorService.generate$(Generators.passphrase, dependencies); - default: - this.log.panic({ algorithm }, `Invalid generator type: "${algorithm}"`); - } - } - /** Lists the credential types supported by the component. */ protected passwordOptions$ = new BehaviorSubject[]>([]); + /** Determines when the password/passphrase selector is visible. */ + protected showCredentialTypes$ = this.passwordOptions$.pipe(map((options) => options.length > 1)); + /** tracks the currently selected credential type */ - protected algorithm$ = new ReplaySubject(1); + protected maybeAlgorithm$ = new ReplaySubject(1); + + /** tracks the last valid algorithm selection */ + protected algorithm$ = this.maybeAlgorithm$.pipe( + filter((algorithm): algorithm is AlgorithmMetadata => !!algorithm), + ); /** * Emits the copy button aria-label respective of the selected credential type */ protected credentialTypeCopyLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ copy }) => copy), + map(({ i18nKeys: { copyCredential } }) => translate(copyCredential, this.i18nService)), ); /** * Emits the generate button aria-label respective of the selected credential type */ protected credentialTypeGenerateLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ generate }) => generate), + map(({ i18nKeys: { generateCredential } }) => translate(generateCredential, this.i18nService)), ); /** * Emits the copy credential toast respective of the selected credential type */ protected credentialTypeLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ credentialType }) => credentialType), + map(({ i18nKeys: { credentialType } }) => translate(credentialType, this.i18nService)), ); - private toOptions(algorithms: AlgorithmInfo[]) { + private toOptions(algorithms: AlgorithmMetadata[]) { const options: Option[] = algorithms.map((algorithm) => ({ value: algorithm.id, - label: algorithm.name, + label: translate(algorithm.i18nKeys.name, this.i18nService), })); return options; diff --git a/libs/tools/generator/components/src/password-settings.component.ts b/libs/tools/generator/components/src/password-settings.component.ts index 346e9549cd8..965ada38146 100644 --- a/libs/tools/generator/components/src/password-settings.component.ts +++ b/libs/tools/generator/components/src/password-settings.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { OnInit, @@ -17,11 +15,13 @@ import { takeUntil, Subject, map, filter, tap, skip, ReplaySubject, withLatestFr import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { - Generators, CredentialGeneratorService, PasswordGenerationOptions, + BuiltIn, } from "@bitwarden/generator-core"; +import { hasRangeOfValues } from "./util"; + const Controls = Object.freeze({ length: "length", uppercase: "uppercase", @@ -52,9 +52,11 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { ) {} /** Binds the component to a specific user's settings. + * @remarks this is initialized to null but since it's a required input it'll + * never have that value in practice. */ @Input({ required: true }) - account: Account; + account: Account = null!; protected account$ = new ReplaySubject(1); @@ -78,40 +80,40 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Emits settings updates and completes if the settings become unavailable. * @remarks this does not emit the initial settings. If you would like * to receive live settings updates including the initial update, - * use `CredentialGeneratorService.settings$(...)` instead. + * use `CredentialGeneratorService.settings(...)` instead. */ @Output() readonly onUpdated = new EventEmitter(); protected settings = this.formBuilder.group({ - [Controls.length]: [Generators.password.settings.initial.length], - [Controls.uppercase]: [Generators.password.settings.initial.uppercase], - [Controls.lowercase]: [Generators.password.settings.initial.lowercase], - [Controls.number]: [Generators.password.settings.initial.number], - [Controls.special]: [Generators.password.settings.initial.special], - [Controls.minNumber]: [Generators.password.settings.initial.minNumber], - [Controls.minSpecial]: [Generators.password.settings.initial.minSpecial], - [Controls.avoidAmbiguous]: [!Generators.password.settings.initial.ambiguous], + [Controls.length]: [0], + [Controls.uppercase]: [false], + [Controls.lowercase]: [false], + [Controls.number]: [false], + [Controls.special]: [false], + [Controls.minNumber]: [0], + [Controls.minSpecial]: [0], + [Controls.avoidAmbiguous]: [false], }); private get numbers() { - return this.settings.get(Controls.number); + return this.settings.get(Controls.number)!; } private get special() { - return this.settings.get(Controls.special); + return this.settings.get(Controls.special)!; } private get minNumber() { - return this.settings.get(Controls.minNumber); + return this.settings.get(Controls.minNumber)!; } private get minSpecial() { - return this.settings.get(Controls.minSpecial); + return this.settings.get(Controls.minSpecial)!; } async ngOnInit() { - const settings = await this.generatorService.settings(Generators.password, { + const settings = await this.generatorService.settings(BuiltIn.password, { account$: this.account$, }); @@ -130,13 +132,13 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { .subscribe(([state, constraints]) => { let boundariesHint = this.i18nService.t( "spinboxBoundariesHint", - constraints.length.min?.toString(), - constraints.length.max?.toString(), + constraints.length?.min?.toString(), + constraints.length?.max?.toString(), ); - if (state.length <= (constraints.length.recommendation ?? 0)) { + if (state.length <= (constraints.length?.recommendation ?? 0)) { boundariesHint += this.i18nService.t( "passwordLengthRecommendationHint", - constraints.length.recommendation?.toString(), + constraints.length?.recommendation?.toString(), ); } this.lengthBoundariesHint.next(boundariesHint); @@ -147,19 +149,25 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { // explain policy & disable policy-overridden fields this.generatorService - .policy$(Generators.password, { account$: this.account$ }) + .policy$(BuiltIn.password, { account$: this.account$ }) .pipe(takeUntil(this.destroyed$)) .subscribe(({ constraints }) => { - this.policyInEffect = constraints.policyInEffect; + this.policyInEffect = constraints.policyInEffect ?? false; const toggles = [ - [Controls.length, constraints.length.min < constraints.length.max], + [Controls.length, hasRangeOfValues(constraints.length?.min, constraints.length?.max)], [Controls.uppercase, !constraints.uppercase?.readonly], [Controls.lowercase, !constraints.lowercase?.readonly], [Controls.number, !constraints.number?.readonly], [Controls.special, !constraints.special?.readonly], - [Controls.minNumber, constraints.minNumber.min < constraints.minNumber.max], - [Controls.minSpecial, constraints.minSpecial.min < constraints.minSpecial.max], + [ + Controls.minNumber, + hasRangeOfValues(constraints.minNumber?.min, constraints.minNumber?.max), + ], + [ + Controls.minSpecial, + hasRangeOfValues(constraints.minSpecial?.min, constraints.minSpecial?.max), + ], ] as [keyof typeof Controls, boolean][]; for (const [control, enabled] of toggles) { @@ -172,7 +180,7 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { let lastMinNumber = 1; this.numbers.valueChanges .pipe( - filter((checked) => !(checked && this.minNumber.value > 0)), + filter((checked) => !(checked && (this.minNumber.value ?? 0) > 0)), map((checked) => (checked ? lastMinNumber : 0)), takeUntil(this.destroyed$), ) @@ -180,8 +188,11 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { this.minNumber.valueChanges .pipe( - map((value) => [value, value > 0] as const), - tap(([value, checkNumbers]) => (lastMinNumber = checkNumbers ? value : lastMinNumber)), + map((value) => [value, (value ?? 0) > 0] as const), + tap( + ([value, checkNumbers]) => + (lastMinNumber = checkNumbers && value ? value : lastMinNumber), + ), takeUntil(this.destroyed$), ) .subscribe(([, checkNumbers]) => this.numbers.setValue(checkNumbers, { emitEvent: false })); @@ -189,7 +200,7 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { let lastMinSpecial = 1; this.special.valueChanges .pipe( - filter((checked) => !(checked && this.minSpecial.value > 0)), + filter((checked) => !(checked && (this.minSpecial.value ?? 0) > 0)), map((checked) => (checked ? lastMinSpecial : 0)), takeUntil(this.destroyed$), ) @@ -197,8 +208,11 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { this.minSpecial.valueChanges .pipe( - map((value) => [value, value > 0] as const), - tap(([value, checkSpecial]) => (lastMinSpecial = checkSpecial ? value : lastMinSpecial)), + map((value) => [value, (value ?? 0) > 0] as const), + tap( + ([value, checkSpecial]) => + (lastMinSpecial = checkSpecial && value ? value : lastMinSpecial), + ), takeUntil(this.destroyed$), ) .subscribe(([, checkSpecial]) => this.special.setValue(checkSpecial, { emitEvent: false })); @@ -230,7 +244,7 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { } /** display binding for enterprise policy notice */ - protected policyInEffect: boolean; + protected policyInEffect: boolean = false; private lengthBoundariesHint = new ReplaySubject(1); @@ -239,9 +253,9 @@ export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { private toggleEnabled(setting: keyof typeof Controls, enabled: boolean) { if (enabled) { - this.settings.get(setting).enable({ emitEvent: false }); + this.settings.get(setting)?.enable({ emitEvent: false }); } else { - this.settings.get(setting).disable({ emitEvent: false }); + this.settings.get(setting)?.disable({ emitEvent: false }); } } diff --git a/libs/tools/generator/components/src/subaddress-settings.component.ts b/libs/tools/generator/components/src/subaddress-settings.component.ts index b09ecc86f9e..27ed6d5f9f3 100644 --- a/libs/tools/generator/components/src/subaddress-settings.component.ts +++ b/libs/tools/generator/components/src/subaddress-settings.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component, EventEmitter, @@ -13,10 +11,10 @@ import { import { FormBuilder } from "@angular/forms"; import { map, ReplaySubject, skip, Subject, takeUntil, withLatestFrom } from "rxjs"; -import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { CredentialGeneratorService, - Generators, + BuiltIn, SubaddressGenerationOptions, } from "@bitwarden/generator-core"; @@ -28,20 +26,20 @@ import { }) export class SubaddressSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component - * @param accountService queries user availability * @param generatorService settings and policy logic * @param formBuilder reactive form controls */ constructor( private formBuilder: FormBuilder, private generatorService: CredentialGeneratorService, - private accountService: AccountService, ) {} /** Binds the component to a specific user's settings. + * @remarks this is initialized to null but since it's a required input it'll + * never have that value in practice. */ @Input({ required: true }) - account: Account; + account: Account = null!; protected account$ = new ReplaySubject(1); @@ -54,18 +52,18 @@ export class SubaddressSettingsComponent implements OnInit, OnChanges, OnDestroy /** Emits settings updates and completes if the settings become unavailable. * @remarks this does not emit the initial settings. If you would like * to receive live settings updates including the initial update, - * use `CredentialGeneratorService.settings$(...)` instead. + * use `CredentialGeneratorService.settings(...)` instead. */ @Output() readonly onUpdated = new EventEmitter(); /** The template's control bindings */ protected settings = this.formBuilder.group({ - subaddressEmail: [Generators.subaddress.settings.initial.subaddressEmail], + subaddressEmail: [""], }); async ngOnInit() { - const settings = await this.generatorService.settings(Generators.subaddress, { + const settings = await this.generatorService.settings(BuiltIn.plusAddress, { account$: this.account$, }); @@ -79,7 +77,7 @@ export class SubaddressSettingsComponent implements OnInit, OnChanges, OnDestroy this.saveSettings .pipe( withLatestFrom(this.settings.valueChanges), - map(([, settings]) => settings), + map(([, settings]) => settings as SubaddressGenerationOptions), takeUntil(this.destroyed$), ) .subscribe(settings); diff --git a/libs/tools/generator/components/src/username-generator.component.html b/libs/tools/generator/components/src/username-generator.component.html index 533a7a0e543..51b998f1d56 100644 --- a/libs/tools/generator/components/src/username-generator.component.html +++ b/libs/tools/generator/components/src/username-generator.component.html @@ -59,7 +59,7 @@ @@ -69,12 +69,12 @@ [account]="account$ | async" /> diff --git a/libs/tools/generator/components/src/username-generator.component.ts b/libs/tools/generator/components/src/username-generator.component.ts index de48a9bd6b1..6227bcd3f7c 100644 --- a/libs/tools/generator/components/src/username-generator.component.ts +++ b/libs/tools/generator/components/src/username-generator.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { LiveAnnouncer } from "@angular/cdk/a11y"; import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { @@ -25,15 +23,15 @@ import { map, ReplaySubject, Subject, - switchMap, takeUntil, + tap, withLatestFrom, } from "rxjs"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { IntegrationId } from "@bitwarden/common/tools/integration"; +import { VendorId } from "@bitwarden/common/tools/extension"; import { SemanticLogger, disabledSemanticLoggerProvider, @@ -43,21 +41,23 @@ import { UserId } from "@bitwarden/common/types/guid"; import { ToastService, Option } from "@bitwarden/components"; import { AlgorithmInfo, - CredentialAlgorithm, - CredentialCategories, CredentialGeneratorService, GenerateRequest, GeneratedCredential, - Generators, - getForwarderConfiguration, + isForwarderExtensionId, isEmailAlgorithm, - isForwarderIntegration, - isSameAlgorithm, isUsernameAlgorithm, - toCredentialGeneratorConfiguration, + isSameAlgorithm, + CredentialAlgorithm, + AlgorithmMetadata, + AlgorithmsByType, + Type, + Algorithm, } from "@bitwarden/generator-core"; import { GeneratorHistoryService } from "@bitwarden/generator-history"; +import { toAlgorithmInfo, translate } from "./util"; + // constants used to identify navigation selections that are not // generator algorithms const FORWARDER = "forwarder"; @@ -89,11 +89,14 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy private ariaLive: LiveAnnouncer, ) {} + /** exports algorithm symbols to the template */ + protected readonly Algorithm = Algorithm; + /** Binds the component to a specific user's settings. When this input is not provided, * the form binds to the active user */ @Input() - account: Account | null; + account: Account | null = null; protected account$ = new ReplaySubject(1); @@ -110,7 +113,11 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy async ngOnChanges(changes: SimpleChanges) { const account = changes?.account; - if (account?.previousValue?.id !== account?.currentValue?.id) { + if ( + account && + account.currentValue.id && + account.previousValue.id !== account.currentValue.id + ) { this.log.debug( { previousUserId: account?.previousValue?.id as UserId, @@ -118,7 +125,7 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy }, "account input change detected", ); - this.account$.next(this.account); + this.account$.next(account.currentValue.id); } } @@ -134,18 +141,18 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy /** emits algorithm info when the selected algorithm changes */ @Output() - readonly onAlgorithm = new EventEmitter(); + readonly onAlgorithm = new EventEmitter(); /** Removes bottom margin from internal elements */ @Input({ transform: coerceBooleanProperty }) disableMargin = false; /** Tracks the selected generation algorithm */ protected username = this.formBuilder.group({ - nav: [null as string], + nav: [null as string | null], }); protected forwarder = this.formBuilder.group({ - nav: [null as string], + nav: [null as string | null], }); async ngOnInit() { @@ -154,38 +161,63 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy }); if (!this.account) { - this.account = await firstValueFrom(this.accountService.activeAccount$); - this.log.info( - { userId: this.account.id }, - "account not specified; using active account settings", - ); - this.account$.next(this.account); + const account = await firstValueFrom(this.accountService.activeAccount$); + if (!account) { + this.log.panic("active account cannot be `null`."); + } + + this.log.info({ userId: account.id }, "account not specified; using active account settings"); + this.account$.next(account); } - this.generatorService - .algorithms$(["email", "username"], { account$: this.account$ }) + combineLatest([ + this.generatorService.algorithms$("email", { account$: this.account$ }), + this.generatorService.algorithms$("username", { account$: this.account$ }), + ]) .pipe( + map((algorithms) => algorithms.flat()), map((algorithms) => { - const usernames = algorithms.filter((a) => !isForwarderIntegration(a.id)); + // construct options for username and email algorithms; replace forwarder + // entry with a virtual entry for drill-down + const usernames = algorithms.filter((a) => !isForwarderExtensionId(a.id)); + usernames.sort((a, b) => a.weight - b.weight); const usernameOptions = this.toOptions(usernames); - usernameOptions.push({ value: FORWARDER, label: this.i18nService.t("forwardedEmail") }); + usernameOptions.splice(-1, 0, { + value: FORWARDER, + label: this.i18nService.t("forwardedEmail"), + }); - const forwarders = algorithms.filter((a) => isForwarderIntegration(a.id)); + // construct options for forwarder algorithms; they get their own selection box + const forwarders = algorithms.filter((a) => isForwarderExtensionId(a.id)); + forwarders.sort((a, b) => a.weight - b.weight); const forwarderOptions = this.toOptions(forwarders); forwarderOptions.unshift({ value: NONE_SELECTED, label: this.i18nService.t("select") }); return [usernameOptions, forwarderOptions] as const; }), + tap((algorithms) => + this.log.debug({ algorithms: algorithms as object }, "algorithms loaded"), + ), takeUntil(this.destroyed), ) .subscribe(([usernames, forwarders]) => { - this.typeOptions$.next(usernames); - this.forwarderOptions$.next(forwarders); + // update subjects within the angular zone so that the + // template bindings refresh immediately + this.zone.run(() => { + this.typeOptions$.next(usernames); + this.forwarderOptions$.next(forwarders); + }); }); - this.algorithm$ + this.maybeAlgorithm$ .pipe( - map((a) => a?.description), + map((a) => { + if (a?.i18nKeys?.description) { + return translate(a.i18nKeys.description, this.i18nService); + } else { + return ""; + } + }), takeUntil(this.destroyed), ) .subscribe((hint) => { @@ -197,10 +229,12 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy }); // wire up the generator - this.algorithm$ + this.generatorService + .generate$({ + on$: this.generate$, + account$: this.account$, + }) .pipe( - filter((algorithm) => !!algorithm), - switchMap((algorithm) => this.typeToGenerator$(algorithm.id)), catchError((error: unknown, generator) => { if (typeof error === "string") { this.toastService.showToast({ @@ -215,11 +249,14 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy // continue with origin stream return generator; }), - withLatestFrom(this.account$, this.algorithm$), + withLatestFrom(this.account$, this.maybeAlgorithm$), takeUntil(this.destroyed), ) .subscribe(([generated, account, algorithm]) => { - this.log.debug({ source: generated.source }, "credential generated"); + this.log.debug( + { source: generated.source ?? null, algorithm: algorithm?.id ?? null }, + "credential generated", + ); this.generatorHistoryService .track(account.id, generated.credential, generated.category, generated.generationDate) @@ -230,12 +267,12 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy // update subjects within the angular zone so that the // template bindings refresh immediately this.zone.run(() => { - if (generated.source === this.USER_REQUEST) { - this.announce(algorithm.onGeneratedMessage); + if (algorithm && generated.source === this.USER_REQUEST) { + this.announce(translate(algorithm.i18nKeys.credentialGenerated, this.i18nService)); } + this.generatedCredential$.next(generated); this.onGenerated.next(generated); - this.value$.next(generated.credential); }); }); @@ -248,24 +285,31 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy this.username.valueChanges .pipe( - map( - (username): CascadeValue => - username.nav === FORWARDER - ? { nav: username.nav } - : { nav: username.nav, algorithm: JSON.parse(username.nav) }, - ), + map((username): CascadeValue => { + if (username.nav === FORWARDER) { + return { nav: username.nav }; + } else if (username.nav) { + return { nav: username.nav, algorithm: JSON.parse(username.nav) }; + } else { + const [algorithm] = AlgorithmsByType[Type.username]; + return { nav: JSON.stringify(algorithm), algorithm }; + } + }), takeUntil(this.destroyed), ) .subscribe(activeIdentifier$); this.forwarder.valueChanges .pipe( - map( - (forwarder): CascadeValue => - forwarder.nav === NONE_SELECTED - ? { nav: forwarder.nav } - : { nav: forwarder.nav, algorithm: JSON.parse(forwarder.nav) }, - ), + map((forwarder): CascadeValue => { + if (forwarder.nav === NONE_SELECTED) { + return { nav: forwarder.nav }; + } else if (forwarder.nav) { + return { nav: forwarder.nav, algorithm: JSON.parse(forwarder.nav) }; + } else { + return { nav: NONE_SELECTED }; + } + }), takeUntil(this.destroyed), ) .subscribe(activeForwarder$); @@ -276,7 +320,7 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy map(([username, forwarder]) => { const showForwarder = !username.algorithm; const forwarderId = - showForwarder && isForwarderIntegration(forwarder.algorithm) + showForwarder && forwarder.algorithm && isForwarderExtensionId(forwarder.algorithm) ? forwarder.algorithm.forwarder : null; return [showForwarder, forwarderId] as const; @@ -306,57 +350,61 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy return null; } }), - distinctUntilChanged((prev, next) => isSameAlgorithm(prev?.id, next?.id)), + distinctUntilChanged((prev, next) => { + if (prev === null || next === null) { + return false; + } else { + return isSameAlgorithm(prev.id, next.id); + } + }), takeUntil(this.destroyed), ) .subscribe((algorithm) => { - this.log.debug(algorithm, "algorithm selected"); + this.log.debug({ algorithm: algorithm?.id ?? null }, "algorithm selected"); // update subjects within the angular zone so that the // template bindings refresh immediately this.zone.run(() => { - this.algorithm$.next(algorithm); - this.onAlgorithm.next(algorithm); + this.maybeAlgorithm$.next(algorithm); + if (algorithm) { + this.onAlgorithm.next(toAlgorithmInfo(algorithm, this.i18nService)); + } else { + this.onAlgorithm.next(null); + } }); }); // assume the last-visible generator algorithm is the user's preferred one const preferences = await this.generatorService.preferences({ account$: this.account$ }); this.algorithm$ - .pipe( - filter((algorithm) => !!algorithm), - withLatestFrom(preferences), - takeUntil(this.destroyed), - ) + .pipe(withLatestFrom(preferences), takeUntil(this.destroyed)) .subscribe(([algorithm, preference]) => { if (isEmailAlgorithm(algorithm.id)) { - this.log.info( - { algorithm, category: CredentialCategories.email }, - "algorithm preferences updated", - ); preference.email.algorithm = algorithm.id; preference.email.updated = new Date(); } else if (isUsernameAlgorithm(algorithm.id)) { - this.log.info( - { algorithm, category: CredentialCategories.username }, - "algorithm preferences updated", - ); preference.username.algorithm = algorithm.id; preference.username.updated = new Date(); } else { return; } + this.log.info( + { algorithm: algorithm.id, type: algorithm.type }, + "algorithm preferences updated", + ); preferences.next(preference); }); preferences .pipe( map(({ email, username }) => { - const forwarderPref = isForwarderIntegration(email.algorithm) ? email : null; const usernamePref = email.updated > username.updated ? email : username; + const forwarderPref = isForwarderExtensionId(usernamePref.algorithm) + ? usernamePref + : null; - // inject drilldown flags + // inject drill-down flags const forwarderNav = !forwarderPref ? NONE_SELECTED : JSON.stringify(forwarderPref.algorithm); @@ -368,7 +416,7 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy selection: { nav: userNav }, active: { nav: userNav, - algorithm: forwarderPref ? null : usernamePref.algorithm, + algorithm: forwarderPref ? undefined : usernamePref.algorithm, }, }, forwarder: { @@ -385,6 +433,14 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy takeUntil(this.destroyed), ) .subscribe(({ username, forwarder }) => { + this.log.debug( + { + username: username.selection, + forwarder: forwarder.selection, + }, + "navigation updated", + ); + // update navigation; break subscription loop this.username.setValue(username.selection, { emitEvent: false }); this.forwarder.setValue(forwarder.selection, { emitEvent: false }); @@ -396,17 +452,16 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy // automatically regenerate when the algorithm switches if the algorithm // allows it; otherwise set a placeholder - this.algorithm$.pipe(takeUntil(this.destroyed)).subscribe((a) => { + this.maybeAlgorithm$.pipe(takeUntil(this.destroyed)).subscribe((a) => { this.zone.run(() => { - if (!a || a.onlyOnRequest) { - this.log.debug("autogeneration disabled; clearing generated credential"); - this.value$.next("-"); - } else { + if (a?.capabilities?.autogenerate) { this.log.debug("autogeneration enabled"); - this.generate("autogenerate").catch((e: unknown) => { this.log.error(e as object, "a failure occurred during autogeneration"); }); + } else { + this.log.debug("autogeneration disabled; clearing generated credential"); + this.generatedCredential$.next(undefined); } }); }); @@ -414,34 +469,6 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy this.log.debug("component initialized"); } - private typeToGenerator$(algorithm: CredentialAlgorithm) { - const dependencies = { - on$: this.generate$, - account$: this.account$, - }; - - this.log.debug({ algorithm }, "constructing generation stream"); - - switch (algorithm) { - case "catchall": - return this.generatorService.generate$(Generators.catchall, dependencies); - - case "subaddress": - return this.generatorService.generate$(Generators.subaddress, dependencies); - - case "username": - return this.generatorService.generate$(Generators.username, dependencies); - } - - if (isForwarderIntegration(algorithm)) { - const forwarder = getForwarderConfiguration(algorithm.forwarder); - const configuration = toCredentialGeneratorConfiguration(forwarder); - return this.generatorService.generate$(configuration, dependencies); - } - - this.log.panic({ algorithm }, `Invalid generator type: "${algorithm}"`); - } - private announce(message: string) { this.ariaLive.announce(message).catch((e) => this.logService.error(e)); } @@ -450,7 +477,7 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy protected typeOptions$ = new BehaviorSubject[]>([]); /** Tracks the currently selected forwarder. */ - protected forwarderId$ = new BehaviorSubject(null); + protected forwarderId$ = new BehaviorSubject(null); /** Lists the credential types supported by the component. */ protected forwarderOptions$ = new BehaviorSubject[]>([]); @@ -458,19 +485,30 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy /** Tracks forwarder control visibility */ protected showForwarder$ = new BehaviorSubject(false); - /** tracks the currently selected credential type */ - protected algorithm$ = new ReplaySubject(1); + /** tracks the currently selected algorithm; emits `null` when no algorithm selected */ + protected maybeAlgorithm$ = new ReplaySubject(1); + + /** tracks the last valid algorithm selection */ + protected algorithm$ = this.maybeAlgorithm$.pipe( + filter((algorithm): algorithm is AlgorithmMetadata => !!algorithm), + ); /** Emits hint key for the currently selected credential type */ protected credentialTypeHint$ = new ReplaySubject(1); + private readonly generatedCredential$ = new BehaviorSubject( + undefined, + ); + /** Emits the last generated value. */ - protected readonly value$ = new BehaviorSubject(""); + protected readonly value$ = this.generatedCredential$.pipe( + map((generated) => generated?.credential ?? "-"), + ); /** Emits when a new credential is requested */ private readonly generate$ = new Subject(); - protected showAlgorithm$ = this.algorithm$.pipe( + protected showAlgorithm$ = this.maybeAlgorithm$.pipe( combineLatestWith(this.showForwarder$), map(([algorithm, showForwarder]) => (showForwarder ? null : algorithm)), ); @@ -479,24 +517,21 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy * Emits the copy button aria-label respective of the selected credential type */ protected credentialTypeCopyLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ copy }) => copy), + map(({ i18nKeys: { copyCredential } }) => translate(copyCredential, this.i18nService)), ); /** * Emits the generate button aria-label respective of the selected credential type */ protected credentialTypeGenerateLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ generate }) => generate), + map(({ i18nKeys: { generateCredential } }) => translate(generateCredential, this.i18nService)), ); /** * Emits the copy credential toast respective of the selected credential type */ protected credentialTypeLabel$ = this.algorithm$.pipe( - filter((algorithm) => !!algorithm), - map(({ credentialType }) => credentialType), + map(({ i18nKeys: { credentialType } }) => translate(credentialType, this.i18nService)), ); /** Identifies generator requests that were requested by the user */ @@ -507,15 +542,20 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy * origin in the debugger. */ protected async generate(source: string) { - const request = { source, website: this.website }; + const algorithm = await firstValueFrom(this.algorithm$); + const request: GenerateRequest = { source, algorithm: algorithm.id }; + if (this.website) { + request.website = this.website; + } + this.log.debug(request, "generation requested"); this.generate$.next(request); } - private toOptions(algorithms: AlgorithmInfo[]) { + private toOptions(algorithms: AlgorithmMetadata[]) { const options: Option[] = algorithms.map((algorithm) => ({ value: JSON.stringify(algorithm.id), - label: algorithm.name, + label: translate(algorithm.i18nKeys.name, this.i18nService), })); return options; @@ -528,9 +568,11 @@ export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy // finalize subjects this.generate$.complete(); - this.value$.complete(); + this.generatedCredential$.complete(); // finalize component bindings this.onGenerated.complete(); + + this.log.debug("component destroyed"); } } diff --git a/libs/tools/generator/components/src/username-settings.component.ts b/libs/tools/generator/components/src/username-settings.component.ts index ea3cfbd35fb..7a12957f906 100644 --- a/libs/tools/generator/components/src/username-settings.component.ts +++ b/libs/tools/generator/components/src/username-settings.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component, EventEmitter, @@ -17,7 +15,7 @@ import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { CredentialGeneratorService, EffUsernameGenerationOptions, - Generators, + BuiltIn, } from "@bitwarden/generator-core"; /** Options group for usernames */ @@ -28,7 +26,6 @@ import { }) export class UsernameSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component - * @param accountService queries user availability * @param generatorService settings and policy logic * @param formBuilder reactive form controls */ @@ -38,9 +35,11 @@ export class UsernameSettingsComponent implements OnInit, OnChanges, OnDestroy { ) {} /** Binds the component to a specific user's settings. + * @remarks this is initialized to null but since it's a required input it'll + * never have that value in practice. */ @Input({ required: true }) - account: Account; + account: Account = null!; protected account$ = new ReplaySubject(1); @@ -53,19 +52,19 @@ export class UsernameSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Emits settings updates and completes if the settings become unavailable. * @remarks this does not emit the initial settings. If you would like * to receive live settings updates including the initial update, - * use `CredentialGeneratorService.settings$(...)` instead. + * use `CredentialGeneratorService.settings(...)` instead. */ @Output() readonly onUpdated = new EventEmitter(); /** The template's control bindings */ protected settings = this.formBuilder.group({ - wordCapitalize: [Generators.username.settings.initial.wordCapitalize], - wordIncludeNumber: [Generators.username.settings.initial.wordIncludeNumber], + wordCapitalize: [false], + wordIncludeNumber: [false], }); async ngOnInit() { - const settings = await this.generatorService.settings(Generators.username, { + const settings = await this.generatorService.settings(BuiltIn.effWordList, { account$: this.account$, }); @@ -79,7 +78,7 @@ export class UsernameSettingsComponent implements OnInit, OnChanges, OnDestroy { this.saveSettings .pipe( withLatestFrom(this.settings.valueChanges), - map(([, settings]) => settings), + map(([, settings]) => settings as EffUsernameGenerationOptions), takeUntil(this.destroyed$), ) .subscribe(settings); diff --git a/libs/tools/generator/components/src/util.ts b/libs/tools/generator/components/src/util.ts index 95e55d816ce..4b0ce4383de 100644 --- a/libs/tools/generator/components/src/util.ts +++ b/libs/tools/generator/components/src/util.ts @@ -1,71 +1,46 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { ValidatorFn, Validators } from "@angular/forms"; -import { distinctUntilChanged, map, pairwise, pipe, skipWhile, startWith, takeWhile } from "rxjs"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { I18nKeyOrLiteral } from "@bitwarden/common/tools/types"; +import { isI18nKey } from "@bitwarden/common/tools/util"; +import { AlgorithmInfo, AlgorithmMetadata } from "@bitwarden/generator-core"; -import { AnyConstraint, Constraints } from "@bitwarden/common/tools/types"; -import { UserId } from "@bitwarden/common/types/guid"; -import { CredentialGeneratorConfiguration } from "@bitwarden/generator-core"; +/** Adapts {@link AlgorithmMetadata} to legacy {@link AlgorithmInfo} structure. */ +export function toAlgorithmInfo(metadata: AlgorithmMetadata, i18n: I18nService) { + const info: AlgorithmInfo = { + id: metadata.id, + type: metadata.type, + name: translate(metadata.i18nKeys.name, i18n), + generate: translate(metadata.i18nKeys.generateCredential, i18n), + onGeneratedMessage: translate(metadata.i18nKeys.credentialGenerated, i18n), + credentialType: translate(metadata.i18nKeys.credentialType, i18n), + copy: translate(metadata.i18nKeys.copyCredential, i18n), + useGeneratedValue: translate(metadata.i18nKeys.useCredential, i18n), + onlyOnRequest: !metadata.capabilities.autogenerate, + request: metadata.capabilities.fields, + }; -export function completeOnAccountSwitch() { - return pipe( - map(({ id }: { id: UserId | null }) => id), - skipWhile((id) => !id), - startWith(null as UserId), - pairwise(), - takeWhile(([prev, next]) => (prev ?? next) === next), - map(([_, id]) => id), - distinctUntilChanged(), - ); + if (metadata.i18nKeys.description) { + info.description = translate(metadata.i18nKeys.description, i18n); + } + + return info; } -export function toValidators( - target: keyof Settings, - configuration: CredentialGeneratorConfiguration, - policy?: Constraints, -) { - const validators: Array = []; - - // widen the types to avoid typecheck issues - const config: AnyConstraint = configuration.settings.constraints[target]; - const runtime: AnyConstraint = policy[target]; - - const required = getConstraint("required", config, runtime) ?? false; - if (required) { - validators.push(Validators.required); - } - - const maxLength = getConstraint("maxLength", config, runtime); - if (maxLength !== undefined) { - validators.push(Validators.maxLength(maxLength)); - } - - const minLength = getConstraint("minLength", config, runtime); - if (minLength !== undefined) { - validators.push(Validators.minLength(config.minLength)); - } - - const min = getConstraint("min", config, runtime); - if (min !== undefined) { - validators.push(Validators.min(min)); - } - - const max = getConstraint("max", config, runtime); - if (max !== undefined) { - validators.push(Validators.max(max)); - } - - return validators; +/** Translates an internationalization key + * @param key the key to translate + * @param i18n the service providing translations + * @returns the translated key; if the key is a literal the literal + * is returned instead. + */ +export function translate(key: I18nKeyOrLiteral, i18n: I18nService) { + return isI18nKey(key) ? i18n.t(key) : key.literal; } -function getConstraint( - key: Key, - config: AnyConstraint, - policy?: AnyConstraint, -) { - if (policy && key in policy) { - return policy[key] ?? config[key]; - } else if (config && key in config) { - return config[key]; - } +/** Returns true when min < max + * @param min the minimum value to check; when this is nullish it becomes 0. + * @param max the maximum value to check; when this is nullish it becomes +Infinity. + */ +export function hasRangeOfValues(min?: number, max?: number) { + const minimum = min ?? 0; + const maximum = max ?? Number.POSITIVE_INFINITY; + return minimum < maximum; } diff --git a/libs/tools/generator/core/src/abstractions/credential-generator-service.abstraction.ts b/libs/tools/generator/core/src/abstractions/credential-generator-service.abstraction.ts new file mode 100644 index 00000000000..74f78514594 --- /dev/null +++ b/libs/tools/generator/core/src/abstractions/credential-generator-service.abstraction.ts @@ -0,0 +1,104 @@ +import { Observable } from "rxjs"; + +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { BoundDependency, OnDependency } from "@bitwarden/common/tools/dependencies"; +import { VendorId } from "@bitwarden/common/tools/extension"; +import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject"; + +import { + CredentialAlgorithm, + GeneratorMetadata, + GeneratorProfile, + CredentialType, +} from "../metadata"; +import { AlgorithmMetadata } from "../metadata/algorithm-metadata"; +import { + CredentialPreference, + ForwarderOptions, + GeneratedCredential, + GenerateRequest, +} from "../types"; +import { GeneratorConstraints } from "../types/generator-constraints"; + +/** Generates credentials used in identity and/or authentication flows. + */ +export abstract class CredentialGeneratorService { + /** Generates a stream of credentials + * @param dependencies.on$ Required. A new credential is emitted when this emits. + */ + abstract generate$: ( + dependencies: OnDependency & BoundDependency<"account", Account>, + ) => Observable; + + /** Emits metadata for the set of algorithms available to a user. + * @param type the set of algorithms + * @param dependencies.account$ algorithms are filtered to only + * those matching the provided account's policy. + * @returns An observable that emits algorithm metadata. + */ + abstract algorithms$: ( + type: CredentialType, + dependencies: BoundDependency<"account", Account>, + ) => Observable; + + /** Lists metadata for a set of algorithms. + * @param type the type or types of algorithms + * @returns A list containing the requested metadata. + * @remarks this is a raw data interface. To apply rules such as algorithm availability, + * use {@link algorithms$} instead. + */ + abstract algorithms: (type: CredentialType | CredentialType[]) => AlgorithmMetadata[]; + + /** Look up the metadata for a specific generator algorithm + * @param id identifies the algorithm + * @returns the requested metadata, or `null` if the metadata wasn't found. + */ + abstract algorithm: (id: CredentialAlgorithm) => AlgorithmMetadata; + + /** Look up the forwarder metadata for a vendor. + * @param id identifies the vendor proving the forwarder + */ + abstract forwarder: (id: VendorId) => GeneratorMetadata; + + /** Get a subject bound to credential generator preferences. + * @param dependencies.account$ identifies the account to which the preferences are bound + * @returns a subject bound to the user's preferences + * @remarks Preferences determine which algorithms are used when generating a + * credential from a credential category (e.g. `PassX` or `Username`). Preferences + * should not be used to hold navigation history. Use {@link @bitwarden/generator-navigation} + * instead. + */ + abstract preferences: ( + dependencies: BoundDependency<"account", Account>, + ) => UserStateSubject; + + /** Get a subject bound to a specific user's settings. the subject enforces policy for the + * settings by automatically updating incorrect values to those allowed by policy. + * @param metadata determines which generator's settings are loaded + * @param dependencies.account$ identifies the account to which the settings are bound + * @param profile identifies the generator profile to load; when this is not specified + * the user's account profile is loaded. + * @returns a subject bound to the requested user's generator settings + * @remarks Generator metadata can be looked up using {@link BuiltIn} and {@link forwarder}. + */ + abstract settings: ( + metadata: Readonly>, + dependencies: BoundDependency<"account", Account>, + profile?: GeneratorProfile, + ) => UserStateSubject; + + /** Get the policy constraints for the provided configuration + * @param metadata determines which generator's policy is loaded + * @param dependencies.account$ determines which user's policy is loaded + * @param profile identifies the generator profile to load; when this is not specified + * the user's account profile is loaded. + * @returns an observable that emits the policy once `dependencies.account$` + * and the policy become available. + * @remarks Generator metadata can be looked up using {@link BuiltIn} and {@link forwarder}. + */ + abstract policy$: ( + metadata: Readonly>, + dependencies: BoundDependency<"account", Account>, + profile?: GeneratorProfile, + ) => Observable>; +} diff --git a/libs/tools/generator/core/src/abstractions/generator.service.abstraction.ts b/libs/tools/generator/core/src/abstractions/generator.service.abstraction.ts index 221c0b8b007..9c12dba44c7 100644 --- a/libs/tools/generator/core/src/abstractions/generator.service.abstraction.ts +++ b/libs/tools/generator/core/src/abstractions/generator.service.abstraction.ts @@ -9,6 +9,7 @@ import { PolicyEvaluator } from "./policy-evaluator.abstraction"; /** Generates credentials used for user authentication * @typeParam Options the credential generation configuration * @typeParam Policy the policy enforced by the generator + * @deprecated Use {@link CredentialGeneratorService} instead. */ export abstract class GeneratorService { /** An observable monitoring the options saved to disk. diff --git a/libs/tools/generator/core/src/abstractions/index.ts b/libs/tools/generator/core/src/abstractions/index.ts index 471ec89ea32..4f45f985ef2 100644 --- a/libs/tools/generator/core/src/abstractions/index.ts +++ b/libs/tools/generator/core/src/abstractions/index.ts @@ -1,3 +1,4 @@ +export { CredentialGeneratorService } from "./credential-generator-service.abstraction"; export { GeneratorService } from "./generator.service.abstraction"; export { GeneratorStrategy } from "./generator-strategy.abstraction"; export { PolicyEvaluator } from "./policy-evaluator.abstraction"; diff --git a/libs/tools/generator/core/src/data/default-addy-io-options.ts b/libs/tools/generator/core/src/data/default-addy-io-options.ts deleted file mode 100644 index 2ebeefff6a8..00000000000 --- a/libs/tools/generator/core/src/data/default-addy-io-options.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { EmailDomainOptions, SelfHostedApiOptions } from "../types"; - -export const DefaultAddyIoOptions: SelfHostedApiOptions & EmailDomainOptions = Object.freeze({ - website: null, - baseUrl: "https://app.addy.io", - token: "", - domain: "", -}); diff --git a/libs/tools/generator/core/src/data/default-credential-preferences.ts b/libs/tools/generator/core/src/data/default-credential-preferences.ts deleted file mode 100644 index c26d44b3b79..00000000000 --- a/libs/tools/generator/core/src/data/default-credential-preferences.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CredentialPreference } from "../types"; - -import { EmailAlgorithms, PasswordAlgorithms, UsernameAlgorithms } from "./generator-types"; - -export const DefaultCredentialPreferences: CredentialPreference = Object.freeze({ - email: Object.freeze({ - algorithm: EmailAlgorithms[0], - updated: new Date(0), - }), - password: Object.freeze({ - algorithm: PasswordAlgorithms[0], - updated: new Date(0), - }), - username: Object.freeze({ - algorithm: UsernameAlgorithms[0], - updated: new Date(0), - }), -}); diff --git a/libs/tools/generator/core/src/data/default-duck-duck-go-options.ts b/libs/tools/generator/core/src/data/default-duck-duck-go-options.ts deleted file mode 100644 index c600e6e512a..00000000000 --- a/libs/tools/generator/core/src/data/default-duck-duck-go-options.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ApiOptions } from "../types"; - -export const DefaultDuckDuckGoOptions: ApiOptions = Object.freeze({ - website: null, - token: "", -}); diff --git a/libs/tools/generator/core/src/data/default-fastmail-options.ts b/libs/tools/generator/core/src/data/default-fastmail-options.ts deleted file mode 100644 index 18faefc4643..00000000000 --- a/libs/tools/generator/core/src/data/default-fastmail-options.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ApiOptions, EmailPrefixOptions } from "../types"; - -export const DefaultFastmailOptions: ApiOptions & EmailPrefixOptions = Object.freeze({ - website: "", - domain: "", - prefix: "", - token: "", -}); diff --git a/libs/tools/generator/core/src/data/default-firefox-relay-options.ts b/libs/tools/generator/core/src/data/default-firefox-relay-options.ts deleted file mode 100644 index 20433a3e12a..00000000000 --- a/libs/tools/generator/core/src/data/default-firefox-relay-options.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ApiOptions } from "../types"; - -export const DefaultFirefoxRelayOptions: ApiOptions = Object.freeze({ - website: null, - token: "", -}); diff --git a/libs/tools/generator/core/src/data/default-forward-email-options.ts b/libs/tools/generator/core/src/data/default-forward-email-options.ts deleted file mode 100644 index d5175534a05..00000000000 --- a/libs/tools/generator/core/src/data/default-forward-email-options.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ApiOptions, EmailDomainOptions } from "../types"; - -export const DefaultForwardEmailOptions: ApiOptions & EmailDomainOptions = Object.freeze({ - website: null, - token: "", - domain: "", -}); diff --git a/libs/tools/generator/core/src/data/default-simple-login-options.ts b/libs/tools/generator/core/src/data/default-simple-login-options.ts deleted file mode 100644 index 965b1222cd3..00000000000 --- a/libs/tools/generator/core/src/data/default-simple-login-options.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { SelfHostedApiOptions } from "../types"; - -export const DefaultSimpleLoginOptions: SelfHostedApiOptions = Object.freeze({ - website: null, - baseUrl: "https://app.simplelogin.io", - token: "", -}); diff --git a/libs/tools/generator/core/src/data/generator-types.ts b/libs/tools/generator/core/src/data/generator-types.ts deleted file mode 100644 index e54ec34e497..00000000000 --- a/libs/tools/generator/core/src/data/generator-types.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** Types of passwords that may be generated by the credential generator */ -export const PasswordAlgorithms = Object.freeze(["password", "passphrase"] as const); - -/** Types of usernames that may be generated by the credential generator */ -export const UsernameAlgorithms = Object.freeze(["username"] as const); - -/** Types of email addresses that may be generated by the credential generator */ -export const EmailAlgorithms = Object.freeze(["catchall", "subaddress"] as const); - -/** All types of credentials that may be generated by the credential generator */ -export const CredentialAlgorithms = Object.freeze([ - ...PasswordAlgorithms, - ...UsernameAlgorithms, - ...EmailAlgorithms, -] as const); diff --git a/libs/tools/generator/core/src/data/generators.ts b/libs/tools/generator/core/src/data/generators.ts deleted file mode 100644 index da87c60f1f4..00000000000 --- a/libs/tools/generator/core/src/data/generators.ts +++ /dev/null @@ -1,422 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; -import { ApiSettings } from "@bitwarden/common/tools/integration/rpc"; -import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; -import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint"; -import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; - -import { - EmailRandomizer, - ForwarderConfiguration, - PasswordRandomizer, - UsernameRandomizer, -} from "../engine"; -import { Forwarder } from "../engine/forwarder"; -import { - DefaultPolicyEvaluator, - DynamicPasswordPolicyConstraints, - PassphraseGeneratorOptionsEvaluator, - passphraseLeastPrivilege, - PassphrasePolicyConstraints, - PasswordGeneratorOptionsEvaluator, - passwordLeastPrivilege, -} from "../policies"; -import { CatchallConstraints } from "../policies/catchall-constraints"; -import { SubaddressConstraints } from "../policies/subaddress-constraints"; -import { - CatchallGenerationOptions, - CredentialGenerator, - CredentialGeneratorConfiguration, - EffUsernameGenerationOptions, - GeneratorDependencyProvider, - NoPolicy, - PassphraseGenerationOptions, - PassphraseGeneratorPolicy, - PasswordGenerationOptions, - PasswordGeneratorPolicy, - SubaddressGenerationOptions, -} from "../types"; - -import { DefaultCatchallOptions } from "./default-catchall-options"; -import { DefaultEffUsernameOptions } from "./default-eff-username-options"; -import { DefaultPassphraseBoundaries } from "./default-passphrase-boundaries"; -import { DefaultPassphraseGenerationOptions } from "./default-passphrase-generation-options"; -import { DefaultPasswordBoundaries } from "./default-password-boundaries"; -import { DefaultPasswordGenerationOptions } from "./default-password-generation-options"; -import { DefaultSubaddressOptions } from "./default-subaddress-generator-options"; - -const PASSPHRASE: CredentialGeneratorConfiguration< - PassphraseGenerationOptions, - PassphraseGeneratorPolicy -> = Object.freeze({ - id: "passphrase", - category: "password", - nameKey: "passphrase", - generateKey: "generatePassphrase", - onGeneratedMessageKey: "passphraseGenerated", - credentialTypeKey: "passphrase", - copyKey: "copyPassphrase", - useGeneratedValueKey: "useThisPassword", - onlyOnRequest: false, - request: [], - engine: { - create( - dependencies: GeneratorDependencyProvider, - ): CredentialGenerator { - return new PasswordRandomizer(dependencies.randomizer); - }, - }, - settings: { - initial: DefaultPassphraseGenerationOptions, - constraints: { - numWords: { - min: DefaultPassphraseBoundaries.numWords.min, - max: DefaultPassphraseBoundaries.numWords.max, - recommendation: DefaultPassphraseGenerationOptions.numWords, - }, - wordSeparator: { maxLength: 1 }, - }, - account: { - key: "passphraseGeneratorSettings", - target: "object", - format: "plain", - classifier: new PublicClassifier([ - "numWords", - "wordSeparator", - "capitalize", - "includeNumber", - ]), - state: GENERATOR_DISK, - initial: DefaultPassphraseGenerationOptions, - options: { - deserializer: (value) => value, - clearOn: ["logout"], - }, - } satisfies ObjectKey, - }, - policy: { - type: PolicyType.PasswordGenerator, - disabledValue: Object.freeze({ - minNumberWords: 0, - capitalize: false, - includeNumber: false, - }), - combine: passphraseLeastPrivilege, - createEvaluator: (policy) => new PassphraseGeneratorOptionsEvaluator(policy), - toConstraints: (policy) => - new PassphrasePolicyConstraints(policy, PASSPHRASE.settings.constraints), - }, -}); - -const PASSWORD: CredentialGeneratorConfiguration< - PasswordGenerationOptions, - PasswordGeneratorPolicy -> = Object.freeze({ - id: "password", - category: "password", - nameKey: "password", - generateKey: "generatePassword", - onGeneratedMessageKey: "passwordGenerated", - credentialTypeKey: "password", - copyKey: "copyPassword", - useGeneratedValueKey: "useThisPassword", - onlyOnRequest: false, - request: [], - engine: { - create( - dependencies: GeneratorDependencyProvider, - ): CredentialGenerator { - return new PasswordRandomizer(dependencies.randomizer); - }, - }, - settings: { - initial: DefaultPasswordGenerationOptions, - constraints: { - length: { - min: DefaultPasswordBoundaries.length.min, - max: DefaultPasswordBoundaries.length.max, - recommendation: DefaultPasswordGenerationOptions.length, - }, - minNumber: { - min: DefaultPasswordBoundaries.minDigits.min, - max: DefaultPasswordBoundaries.minDigits.max, - }, - minSpecial: { - min: DefaultPasswordBoundaries.minSpecialCharacters.min, - max: DefaultPasswordBoundaries.minSpecialCharacters.max, - }, - }, - account: { - key: "passwordGeneratorSettings", - target: "object", - format: "plain", - classifier: new PublicClassifier([ - "length", - "ambiguous", - "uppercase", - "minUppercase", - "lowercase", - "minLowercase", - "number", - "minNumber", - "special", - "minSpecial", - ]), - state: GENERATOR_DISK, - initial: DefaultPasswordGenerationOptions, - options: { - deserializer: (value) => value, - clearOn: ["logout"], - }, - } satisfies ObjectKey, - }, - policy: { - type: PolicyType.PasswordGenerator, - disabledValue: Object.freeze({ - minLength: 0, - useUppercase: false, - useLowercase: false, - useNumbers: false, - numberCount: 0, - useSpecial: false, - specialCount: 0, - }), - combine: passwordLeastPrivilege, - createEvaluator: (policy) => new PasswordGeneratorOptionsEvaluator(policy), - toConstraints: (policy) => - new DynamicPasswordPolicyConstraints(policy, PASSWORD.settings.constraints), - }, -}); - -const USERNAME: CredentialGeneratorConfiguration = - Object.freeze({ - id: "username", - category: "username", - nameKey: "randomWord", - generateKey: "generateUsername", - onGeneratedMessageKey: "usernameGenerated", - credentialTypeKey: "username", - copyKey: "copyUsername", - useGeneratedValueKey: "useThisUsername", - onlyOnRequest: false, - request: [], - engine: { - create( - dependencies: GeneratorDependencyProvider, - ): CredentialGenerator { - return new UsernameRandomizer(dependencies.randomizer); - }, - }, - settings: { - initial: DefaultEffUsernameOptions, - constraints: {}, - account: { - key: "effUsernameGeneratorSettings", - target: "object", - format: "plain", - classifier: new PublicClassifier([ - "wordCapitalize", - "wordIncludeNumber", - ]), - state: GENERATOR_DISK, - initial: DefaultEffUsernameOptions, - options: { - deserializer: (value) => value, - clearOn: ["logout"], - }, - } satisfies ObjectKey, - }, - policy: { - type: PolicyType.PasswordGenerator, - disabledValue: {}, - combine(_acc: NoPolicy, _policy: Policy) { - return {}; - }, - createEvaluator(_policy: NoPolicy) { - return new DefaultPolicyEvaluator(); - }, - toConstraints(_policy: NoPolicy) { - return new IdentityConstraint(); - }, - }, - }); - -const CATCHALL: CredentialGeneratorConfiguration = - Object.freeze({ - id: "catchall", - category: "email", - nameKey: "catchallEmail", - descriptionKey: "catchallEmailDesc", - generateKey: "generateEmail", - onGeneratedMessageKey: "emailGenerated", - credentialTypeKey: "email", - copyKey: "copyEmail", - useGeneratedValueKey: "useThisEmail", - onlyOnRequest: false, - request: [], - engine: { - create( - dependencies: GeneratorDependencyProvider, - ): CredentialGenerator { - return new EmailRandomizer(dependencies.randomizer); - }, - }, - settings: { - initial: DefaultCatchallOptions, - constraints: { catchallDomain: { minLength: 1 } }, - account: { - key: "catchallGeneratorSettings", - target: "object", - format: "plain", - classifier: new PublicClassifier([ - "catchallType", - "catchallDomain", - ]), - state: GENERATOR_DISK, - initial: { - catchallType: "random", - catchallDomain: "", - }, - options: { - deserializer: (value) => value, - clearOn: ["logout"], - }, - } satisfies ObjectKey, - }, - policy: { - type: PolicyType.PasswordGenerator, - disabledValue: {}, - combine(_acc: NoPolicy, _policy: Policy) { - return {}; - }, - createEvaluator(_policy: NoPolicy) { - return new DefaultPolicyEvaluator(); - }, - toConstraints(_policy: NoPolicy, email: string) { - return new CatchallConstraints(email); - }, - }, - }); - -const SUBADDRESS: CredentialGeneratorConfiguration = - Object.freeze({ - id: "subaddress", - category: "email", - nameKey: "plusAddressedEmail", - descriptionKey: "plusAddressedEmailDesc", - generateKey: "generateEmail", - onGeneratedMessageKey: "emailGenerated", - credentialTypeKey: "email", - copyKey: "copyEmail", - useGeneratedValueKey: "useThisEmail", - onlyOnRequest: false, - request: [], - engine: { - create( - dependencies: GeneratorDependencyProvider, - ): CredentialGenerator { - return new EmailRandomizer(dependencies.randomizer); - }, - }, - settings: { - initial: DefaultSubaddressOptions, - constraints: {}, - account: { - key: "subaddressGeneratorSettings", - target: "object", - format: "plain", - classifier: new PublicClassifier([ - "subaddressType", - "subaddressEmail", - ]), - state: GENERATOR_DISK, - initial: { - subaddressType: "random", - subaddressEmail: "", - }, - options: { - deserializer: (value) => value, - clearOn: ["logout"], - }, - } satisfies ObjectKey, - }, - policy: { - type: PolicyType.PasswordGenerator, - disabledValue: {}, - combine(_acc: NoPolicy, _policy: Policy) { - return {}; - }, - createEvaluator(_policy: NoPolicy) { - return new DefaultPolicyEvaluator(); - }, - toConstraints(_policy: NoPolicy, email: string) { - return new SubaddressConstraints(email); - }, - }, - }); - -export function toCredentialGeneratorConfiguration( - configuration: ForwarderConfiguration, -) { - const forwarder = Object.freeze({ - id: { forwarder: configuration.id }, - category: "email", - nameKey: configuration.name, - descriptionKey: "forwardedEmailDesc", - generateKey: "generateEmail", - onGeneratedMessageKey: "emailGenerated", - credentialTypeKey: "email", - copyKey: "copyEmail", - useGeneratedValueKey: "useThisEmail", - onlyOnRequest: true, - request: configuration.forwarder.request, - engine: { - create(dependencies: GeneratorDependencyProvider) { - // FIXME: figure out why `configuration` fails to typecheck - const config: any = configuration; - return new Forwarder(config, dependencies.client, dependencies.i18nService); - }, - }, - settings: { - initial: configuration.forwarder.defaultSettings, - constraints: configuration.forwarder.settingsConstraints, - account: configuration.forwarder.local.settings, - }, - policy: { - type: PolicyType.PasswordGenerator, - disabledValue: {}, - combine(_acc: NoPolicy, _policy: Policy) { - return {}; - }, - createEvaluator(_policy: NoPolicy) { - return new DefaultPolicyEvaluator(); - }, - toConstraints(_policy: NoPolicy) { - return new IdentityConstraint(); - }, - }, - } satisfies CredentialGeneratorConfiguration); - - return forwarder; -} - -/** Generator configurations */ -export const Generators = Object.freeze({ - /** Passphrase generator configuration */ - passphrase: PASSPHRASE, - - /** Password generator configuration */ - password: PASSWORD, - - /** Username generator configuration */ - username: USERNAME, - - /** Catchall email generator configuration */ - catchall: CATCHALL, - - /** Email subaddress generator configuration */ - subaddress: SUBADDRESS, -}); diff --git a/libs/tools/generator/core/src/data/index.ts b/libs/tools/generator/core/src/data/index.ts index 482703fd3c3..bcf57e98c9a 100644 --- a/libs/tools/generator/core/src/data/index.ts +++ b/libs/tools/generator/core/src/data/index.ts @@ -1,20 +1,8 @@ -export * from "./generators"; -export * from "./default-addy-io-options"; export * from "./default-catchall-options"; -export * from "./default-duck-duck-go-options"; -export * from "./default-fastmail-options"; -export * from "./default-forward-email-options"; export * from "./default-passphrase-boundaries"; export * from "./default-password-boundaries"; export * from "./default-eff-username-options"; -export * from "./default-firefox-relay-options"; export * from "./default-passphrase-generation-options"; export * from "./default-password-generation-options"; -export * from "./default-credential-preferences"; export * from "./default-subaddress-generator-options"; -export * from "./default-simple-login-options"; -export * from "./forwarders"; export * from "./integrations"; -export * from "./policies"; -export * from "./username-digits"; -export * from "./generator-types"; diff --git a/libs/tools/generator/core/src/data/policies.ts b/libs/tools/generator/core/src/data/policies.ts deleted file mode 100644 index 4e46718a395..00000000000 --- a/libs/tools/generator/core/src/data/policies.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - PassphraseGenerationOptions, - PassphraseGeneratorPolicy, - PasswordGenerationOptions, - PasswordGeneratorPolicy, - PolicyConfiguration, -} from "../types"; - -import { Generators } from "./generators"; - -/** Policy configurations - * @deprecated use Generator.*.policy instead - */ -export const Policies = Object.freeze({ - Passphrase: Generators.passphrase.policy, - Password: Generators.password.policy, -} satisfies { - /** Passphrase policy configuration */ - Passphrase: PolicyConfiguration; - - /** Password policy configuration */ - Password: PolicyConfiguration; -}); diff --git a/libs/tools/generator/core/src/data/username-digits.ts b/libs/tools/generator/core/src/data/username-digits.ts deleted file mode 100644 index 99ef15cf1ca..00000000000 --- a/libs/tools/generator/core/src/data/username-digits.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const UsernameDigits = Object.freeze({ - enabled: 4, - disabled: 0, -}); diff --git a/libs/tools/generator/core/src/engine/email-randomizer.spec.ts b/libs/tools/generator/core/src/engine/email-randomizer.spec.ts index fb953af1659..2ebe50d12d4 100644 --- a/libs/tools/generator/core/src/engine/email-randomizer.spec.ts +++ b/libs/tools/generator/core/src/engine/email-randomizer.spec.ts @@ -2,6 +2,8 @@ import { mock } from "jest-mock-extended"; import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; +import { Algorithm, Type } from "../metadata"; + import { Randomizer } from "./abstractions"; import { EmailRandomizer } from "./email-randomizer"; @@ -41,7 +43,8 @@ describe("EmailRandomizer", () => { async (email) => { const emailRandomizer = new EmailRandomizer(randomizer); - const result = await emailRandomizer.randomAsciiSubaddress(email); + // this tests what happens when the type system is subverted + const result = await emailRandomizer.randomAsciiSubaddress(email!); expect(result).toEqual(""); }, @@ -100,7 +103,8 @@ describe("EmailRandomizer", () => { it.each([[null], [undefined], [""]])("returns null if the domain is %p", async (domain) => { const emailRandomizer = new EmailRandomizer(randomizer); - const result = await emailRandomizer.randomAsciiCatchall(domain); + // this tests what happens when the type system is subverted + const result = await emailRandomizer.randomAsciiCatchall(domain!); expect(result).toBeNull(); }); @@ -150,7 +154,8 @@ describe("EmailRandomizer", () => { it.each([[null], [undefined], [""]])("returns null if the domain is %p", async (domain) => { const emailRandomizer = new EmailRandomizer(randomizer); - const result = await emailRandomizer.randomWordsCatchall(domain); + // this tests what happens when the type system is subverted + const result = await emailRandomizer.randomWordsCatchall(domain!); expect(result).toBeNull(); }); @@ -214,32 +219,32 @@ describe("EmailRandomizer", () => { const email = new EmailRandomizer(randomizer); const result = await email.generate( - {}, + { algorithm: Algorithm.catchall }, { catchallDomain: "example.com", }, ); - expect(result.category).toEqual("catchall"); + expect(result.category).toEqual(Type.email); }); it("processes subaddress generation options", async () => { const email = new EmailRandomizer(randomizer); const result = await email.generate( - {}, + { algorithm: Algorithm.plusAddress }, { subaddressEmail: "foo@example.com", }, ); - expect(result.category).toEqual("subaddress"); + expect(result.category).toEqual(Type.email); }); it("throws when it cannot recognize the options type", async () => { const email = new EmailRandomizer(randomizer); - const result = email.generate({}, {}); + const result = email.generate({ algorithm: Algorithm.password }, {}); await expect(result).rejects.toBeInstanceOf(Error); }); diff --git a/libs/tools/generator/core/src/engine/email-randomizer.ts b/libs/tools/generator/core/src/engine/email-randomizer.ts index 0be95a975af..f673ba05fc0 100644 --- a/libs/tools/generator/core/src/engine/email-randomizer.ts +++ b/libs/tools/generator/core/src/engine/email-randomizer.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; +import { Type } from "../metadata"; import { CatchallGenerationOptions, CredentialGenerator, @@ -128,7 +129,7 @@ export class EmailRandomizer return new GeneratedCredential( email, - "catchall", + Type.email, Date.now(), request.source, request.website, @@ -138,7 +139,7 @@ export class EmailRandomizer return new GeneratedCredential( email, - "subaddress", + Type.email, Date.now(), request.source, request.website, diff --git a/libs/tools/generator/core/src/engine/forwarder.ts b/libs/tools/generator/core/src/engine/forwarder.ts index 6c6e574e873..5f41e35d21d 100644 --- a/libs/tools/generator/core/src/engine/forwarder.ts +++ b/libs/tools/generator/core/src/engine/forwarder.ts @@ -8,6 +8,7 @@ import { } from "@bitwarden/common/tools/integration/rpc"; import { GenerationRequest } from "@bitwarden/common/tools/types"; +import { Type } from "../metadata"; import { CredentialGenerator, GeneratedCredential } from "../types"; import { AccountRequest, ForwarderConfiguration } from "./forwarder-configuration"; @@ -40,9 +41,8 @@ export class Forwarder implements CredentialGenerator { const create = this.createForwardingAddress(this.configuration, settings); const result = await this.client.fetchJson(create, requestOptions); - const id = { forwarder: this.configuration.id }; - return new GeneratedCredential(result, id, Date.now()); + return new GeneratedCredential(result, Type.email, Date.now()); } private createContext( diff --git a/libs/tools/generator/core/src/engine/password-randomizer.spec.ts b/libs/tools/generator/core/src/engine/password-randomizer.spec.ts index fca98855fd5..a36c4bb5352 100644 --- a/libs/tools/generator/core/src/engine/password-randomizer.spec.ts +++ b/libs/tools/generator/core/src/engine/password-randomizer.spec.ts @@ -3,6 +3,7 @@ import { mock } from "jest-mock-extended"; import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; import { Randomizer } from "../abstractions"; +import { Algorithm, Type } from "../metadata"; import { Ascii } from "./data"; import { PasswordRandomizer } from "./password-randomizer"; @@ -341,32 +342,32 @@ describe("PasswordRandomizer", () => { const password = new PasswordRandomizer(randomizer); const result = await password.generate( - {}, + { algorithm: Algorithm.password }, { length: 10, }, ); - expect(result.category).toEqual("password"); + expect(result.category).toEqual(Type.password); }); it("processes passphrase generation options", async () => { const password = new PasswordRandomizer(randomizer); const result = await password.generate( - {}, + { algorithm: Algorithm.passphrase }, { numWords: 10, }, ); - expect(result.category).toEqual("passphrase"); + expect(result.category).toEqual(Type.password); }); it("throws when it cannot recognize the options type", async () => { const password = new PasswordRandomizer(randomizer); - const result = password.generate({}, {}); + const result = password.generate({ algorithm: Algorithm.username }, {}); await expect(result).rejects.toBeInstanceOf(Error); }); diff --git a/libs/tools/generator/core/src/engine/password-randomizer.ts b/libs/tools/generator/core/src/engine/password-randomizer.ts index a9612d2fb45..dc61ee064e1 100644 --- a/libs/tools/generator/core/src/engine/password-randomizer.ts +++ b/libs/tools/generator/core/src/engine/password-randomizer.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; +import { Type } from "../metadata"; import { CredentialGenerator, GenerateRequest, @@ -86,7 +87,7 @@ export class PasswordRandomizer return new GeneratedCredential( password, - "password", + Type.password, Date.now(), request.source, request.website, @@ -97,7 +98,7 @@ export class PasswordRandomizer return new GeneratedCredential( passphrase, - "passphrase", + Type.password, Date.now(), request.source, request.website, diff --git a/libs/tools/generator/core/src/engine/username-randomizer.spec.ts b/libs/tools/generator/core/src/engine/username-randomizer.spec.ts index 54d140e4469..be0650fe16e 100644 --- a/libs/tools/generator/core/src/engine/username-randomizer.spec.ts +++ b/libs/tools/generator/core/src/engine/username-randomizer.spec.ts @@ -2,6 +2,8 @@ import { mock } from "jest-mock-extended"; import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; +import { Algorithm, Type } from "../metadata"; + import { Randomizer } from "./abstractions"; import { UsernameRandomizer } from "./username-randomizer"; @@ -108,19 +110,19 @@ describe("UsernameRandomizer", () => { const username = new UsernameRandomizer(randomizer); const result = await username.generate( - {}, + { algorithm: Algorithm.username }, { wordIncludeNumber: true, }, ); - expect(result.category).toEqual("username"); + expect(result.category).toEqual(Type.username); }); it("throws when it cannot recognize the options type", async () => { const username = new UsernameRandomizer(randomizer); - const result = username.generate({}, {}); + const result = username.generate({ algorithm: Algorithm.passphrase }, {}); await expect(result).rejects.toBeInstanceOf(Error); }); diff --git a/libs/tools/generator/core/src/index.ts b/libs/tools/generator/core/src/index.ts index 494d034b674..928e6786ff9 100644 --- a/libs/tools/generator/core/src/index.ts +++ b/libs/tools/generator/core/src/index.ts @@ -3,13 +3,34 @@ export * from "./abstractions"; export * from "./data"; export { createRandomizer } from "./factories"; export * from "./types"; -export { CredentialGeneratorService } from "./services"; +export { DefaultCredentialGeneratorService } from "./services"; +export { + CredentialType, + CredentialAlgorithm, + PasswordAlgorithm, + Algorithm, + BuiltIn, + Type, + Profile, + GeneratorMetadata, + GeneratorProfile, + AlgorithmMetadata, + AlgorithmsByType, +} from "./metadata"; +export { + isForwarderExtensionId, + isEmailAlgorithm, + isUsernameAlgorithm, + isPasswordAlgorithm, + isSameAlgorithm, +} from "./metadata/util"; // These internal interfacess are exposed for use by other generator modules // They are unstable and may change arbitrarily export * as engine from "./engine"; export * as integration from "./integration"; export * as policies from "./policies"; +export * as providers from "./providers"; export * as rx from "./rx"; export * as services from "./services"; export * as strategies from "./strategies"; diff --git a/libs/tools/generator/core/src/integration/addy-io.ts b/libs/tools/generator/core/src/integration/addy-io.ts index 93ffed3392a..bd1be0ee6ae 100644 --- a/libs/tools/generator/core/src/integration/addy-io.ts +++ b/libs/tools/generator/core/src/integration/addy-io.ts @@ -4,6 +4,7 @@ import { UserKeyDefinition, } from "@bitwarden/common/platform/state"; import { VendorId } from "@bitwarden/common/tools/extension"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration"; import { ApiSettings, @@ -101,7 +102,7 @@ const forwarder = Object.freeze({ export const AddyIo = Object.freeze({ // integration - id: "anonaddy" as IntegrationId & VendorId, + id: Vendor.addyio as IntegrationId & VendorId, name: "Addy.io", extends: ["forwarder"], diff --git a/libs/tools/generator/core/src/integration/duck-duck-go.ts b/libs/tools/generator/core/src/integration/duck-duck-go.ts index d2bd6173a14..b88a7f77a33 100644 --- a/libs/tools/generator/core/src/integration/duck-duck-go.ts +++ b/libs/tools/generator/core/src/integration/duck-duck-go.ts @@ -4,6 +4,7 @@ import { UserKeyDefinition, } from "@bitwarden/common/platform/state"; import { VendorId } from "@bitwarden/common/tools/extension"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration"; import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc"; import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier"; @@ -90,7 +91,7 @@ const forwarder = Object.freeze({ // integration-wide configuration export const DuckDuckGo = Object.freeze({ - id: "duckduckgo" as IntegrationId & VendorId, + id: Vendor.duckduckgo as IntegrationId & VendorId, name: "DuckDuckGo", baseUrl: "https://quack.duckduckgo.com/api", selfHost: "never", diff --git a/libs/tools/generator/core/src/integration/fastmail.ts b/libs/tools/generator/core/src/integration/fastmail.ts index bfde1aa70f5..a540807666e 100644 --- a/libs/tools/generator/core/src/integration/fastmail.ts +++ b/libs/tools/generator/core/src/integration/fastmail.ts @@ -6,6 +6,7 @@ import { UserKeyDefinition, } from "@bitwarden/common/platform/state"; import { VendorId } from "@bitwarden/common/tools/extension"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration"; import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc"; import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier"; @@ -160,7 +161,7 @@ const forwarder = Object.freeze({ // integration-wide configuration export const Fastmail = Object.freeze({ - id: "fastmail" as IntegrationId & VendorId, + id: Vendor.fastmail as IntegrationId & VendorId, name: "Fastmail", baseUrl: "https://api.fastmail.com", selfHost: "maybe", diff --git a/libs/tools/generator/core/src/integration/firefox-relay.ts b/libs/tools/generator/core/src/integration/firefox-relay.ts index f80de0c95dd..9fbd56aa6ed 100644 --- a/libs/tools/generator/core/src/integration/firefox-relay.ts +++ b/libs/tools/generator/core/src/integration/firefox-relay.ts @@ -4,6 +4,7 @@ import { UserKeyDefinition, } from "@bitwarden/common/platform/state"; import { VendorId } from "@bitwarden/common/tools/extension"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration"; import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc"; import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier"; @@ -98,7 +99,7 @@ const forwarder = Object.freeze({ // integration-wide configuration export const FirefoxRelay = Object.freeze({ - id: "firefoxrelay" as IntegrationId & VendorId, + id: Vendor.mozilla as IntegrationId & VendorId, name: "Firefox Relay", baseUrl: "https://relay.firefox.com/api", selfHost: "never", diff --git a/libs/tools/generator/core/src/integration/forward-email.ts b/libs/tools/generator/core/src/integration/forward-email.ts index 34b4602b94b..b53fc4ffab6 100644 --- a/libs/tools/generator/core/src/integration/forward-email.ts +++ b/libs/tools/generator/core/src/integration/forward-email.ts @@ -4,6 +4,7 @@ import { UserKeyDefinition, } from "@bitwarden/common/platform/state"; import { VendorId } from "@bitwarden/common/tools/extension"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration"; import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc"; import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier"; @@ -102,7 +103,7 @@ const forwarder = Object.freeze({ export const ForwardEmail = Object.freeze({ // integration metadata - id: "forwardemail" as IntegrationId & VendorId, + id: Vendor.forwardemail as IntegrationId & VendorId, name: "Forward Email", extends: ["forwarder"], diff --git a/libs/tools/generator/core/src/integration/simple-login.ts b/libs/tools/generator/core/src/integration/simple-login.ts index efbac69cec2..f3cc776d401 100644 --- a/libs/tools/generator/core/src/integration/simple-login.ts +++ b/libs/tools/generator/core/src/integration/simple-login.ts @@ -4,6 +4,7 @@ import { UserKeyDefinition, } from "@bitwarden/common/platform/state"; import { VendorId } from "@bitwarden/common/tools/extension"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration"; import { ApiSettings, @@ -104,7 +105,7 @@ const forwarder = Object.freeze({ // integration-wide configuration export const SimpleLogin = Object.freeze({ - id: "simplelogin" as IntegrationId & VendorId, + id: Vendor.simplelogin as IntegrationId & VendorId, name: "SimpleLogin", selfHost: "maybe", extends: ["forwarder"], diff --git a/libs/tools/generator/core/src/metadata/algorithm-metadata.ts b/libs/tools/generator/core/src/metadata/algorithm-metadata.ts index c07deef5535..8bffa630dd9 100644 --- a/libs/tools/generator/core/src/metadata/algorithm-metadata.ts +++ b/libs/tools/generator/core/src/metadata/algorithm-metadata.ts @@ -1,6 +1,6 @@ -import { CredentialAlgorithm, CredentialType } from "./type"; +import { I18nKeyOrLiteral } from "@bitwarden/common/tools/types"; -type I18nKeyOrLiteral = string | { literal: string }; +import { CredentialAlgorithm, CredentialType } from "./type"; /** Credential generator metadata common across credential generators */ export type AlgorithmMetadata = { @@ -14,7 +14,7 @@ export type AlgorithmMetadata = { id: CredentialAlgorithm; /** The kind of credential generated by this configuration */ - category: CredentialType; + type: CredentialType; /** Used to order credential algorithms for display purposes. * Items with lesser weights appear before entries with greater @@ -23,6 +23,10 @@ export type AlgorithmMetadata = { weight: number; /** Localization keys */ + // FIXME: in practice, keys like `credentialGenerated` all align + // with credential types and contain duplicate keys. Extract + // them into a "credential type metadata" type and integrate + // that type with the algorithm metadata instead. i18nKeys: { /** descriptive name of the algorithm */ name: I18nKeyOrLiteral; diff --git a/libs/tools/generator/core/src/metadata/email/catchall.spec.ts b/libs/tools/generator/core/src/metadata/email/catchall.spec.ts index d6cc1795e0b..1099a6d59ea 100644 --- a/libs/tools/generator/core/src/metadata/email/catchall.spec.ts +++ b/libs/tools/generator/core/src/metadata/email/catchall.spec.ts @@ -2,7 +2,8 @@ import { mock } from "jest-mock-extended"; import { EmailRandomizer } from "../../engine"; import { CatchallConstraints } from "../../policies/catchall-constraints"; -import { CatchallGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CatchallGenerationOptions } from "../../types"; import { Profile } from "../data"; import { CoreProfileMetadata } from "../profile-metadata"; import { isCoreProfile } from "../util"; diff --git a/libs/tools/generator/core/src/metadata/email/catchall.ts b/libs/tools/generator/core/src/metadata/email/catchall.ts index 0711e5c3719..991f6b18b73 100644 --- a/libs/tools/generator/core/src/metadata/email/catchall.ts +++ b/libs/tools/generator/core/src/metadata/email/catchall.ts @@ -4,17 +4,14 @@ import { deepFreeze } from "@bitwarden/common/tools/util"; import { EmailRandomizer } from "../../engine"; import { CatchallConstraints } from "../../policies/catchall-constraints"; -import { - CatchallGenerationOptions, - CredentialGenerator, - GeneratorDependencyProvider, -} from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CatchallGenerationOptions, CredentialGenerator } from "../../types"; import { Algorithm, Type, Profile } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; const catchall: GeneratorMetadata = deepFreeze({ id: Algorithm.catchall, - category: Type.email, + type: Type.email, weight: 210, i18nKeys: { name: "catchallEmail", diff --git a/libs/tools/generator/core/src/metadata/email/forwarder.ts b/libs/tools/generator/core/src/metadata/email/forwarder.ts index f4f150f33fa..1066f890ef8 100644 --- a/libs/tools/generator/core/src/metadata/email/forwarder.ts +++ b/libs/tools/generator/core/src/metadata/email/forwarder.ts @@ -1,19 +1,14 @@ import { ExtensionMetadata, ExtensionStorageKey } from "@bitwarden/common/tools/extension/type"; -import { SelfHostedApiSettings } from "@bitwarden/common/tools/integration/rpc"; import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint"; import { getForwarderConfiguration } from "../../data"; -import { EmailDomainSettings, EmailPrefixSettings } from "../../engine"; import { Forwarder } from "../../engine/forwarder"; -import { GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { ForwarderOptions } from "../../types"; import { Profile, Type } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; import { ForwarderProfileMetadata } from "../profile-metadata"; -// These options are used by all forwarders; each forwarder uses a different set, -// as defined by `GeneratorMetadata.capabilities.fields`. -type ForwarderOptions = Partial; - // update the extension metadata export function toForwarderMetadata( extension: ExtensionMetadata, @@ -28,7 +23,7 @@ export function toForwarderMetadata( const generator: GeneratorMetadata = { id: { forwarder: extension.product.vendor.id }, - category: Type.email, + type: Type.email, weight: 300, i18nKeys: { name, @@ -56,6 +51,12 @@ export function toForwarderMetadata( storage: { key: "forwarder", frame: 512, + initial: { + token: "", + baseUrl: "", + domain: "", + prefix: "", + }, options: { deserializer: (value) => value, clearOn: ["logout"], diff --git a/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts b/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts index 063cb71c23a..befc900ceab 100644 --- a/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts +++ b/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts @@ -2,7 +2,8 @@ import { mock } from "jest-mock-extended"; import { EmailRandomizer } from "../../engine"; import { SubaddressConstraints } from "../../policies/subaddress-constraints"; -import { SubaddressGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { SubaddressGenerationOptions } from "../../types"; import { Profile } from "../data"; import { CoreProfileMetadata } from "../profile-metadata"; import { isCoreProfile } from "../util"; diff --git a/libs/tools/generator/core/src/metadata/email/plus-address.ts b/libs/tools/generator/core/src/metadata/email/plus-address.ts index 0db0acd415c..940d6599442 100644 --- a/libs/tools/generator/core/src/metadata/email/plus-address.ts +++ b/libs/tools/generator/core/src/metadata/email/plus-address.ts @@ -4,17 +4,14 @@ import { deepFreeze } from "@bitwarden/common/tools/util"; import { EmailRandomizer } from "../../engine"; import { SubaddressConstraints } from "../../policies/subaddress-constraints"; -import { - CredentialGenerator, - GeneratorDependencyProvider, - SubaddressGenerationOptions, -} from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CredentialGenerator, SubaddressGenerationOptions } from "../../types"; import { Algorithm, Profile, Type } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; const plusAddress: GeneratorMetadata = deepFreeze({ id: Algorithm.plusAddress, - category: Type.email, + type: Type.email, weight: 200, i18nKeys: { name: "plusAddressedEmail", diff --git a/libs/tools/generator/core/src/metadata/generator-metadata.ts b/libs/tools/generator/core/src/metadata/generator-metadata.ts index 9296d30430e..704ce88b217 100644 --- a/libs/tools/generator/core/src/metadata/generator-metadata.ts +++ b/libs/tools/generator/core/src/metadata/generator-metadata.ts @@ -1,4 +1,5 @@ -import { CredentialGenerator, GeneratorDependencyProvider } from "../types"; +import { GeneratorDependencyProvider } from "../providers"; +import { CredentialGenerator } from "../types"; import { AlgorithmMetadata } from "./algorithm-metadata"; import { Profile } from "./data"; diff --git a/libs/tools/generator/core/src/metadata/index.ts b/libs/tools/generator/core/src/metadata/index.ts index d9437822270..17c02918705 100644 --- a/libs/tools/generator/core/src/metadata/index.ts +++ b/libs/tools/generator/core/src/metadata/index.ts @@ -3,7 +3,32 @@ import { AlgorithmsByType as AlgorithmsByTypeData, Type as TypeData, } from "./data"; +import catchall from "./email/catchall"; +import plusAddress from "./email/plus-address"; +import passphrase from "./password/eff-word-list"; +import password from "./password/random-password"; import { CredentialType, CredentialAlgorithm } from "./type"; +import effWordList from "./username/eff-word-list"; + +/** Credential generators hosted natively by the credential generator system. + * These are supplemented by generators from the {@link ExtensionService}. + */ +export const BuiltIn = Object.freeze({ + /** Catchall email address generator */ + catchall, + + /** plus-addressed email address generator */ + plusAddress, + + /** passphrase generator using the EFF word list */ + passphrase, + + /** password generator */ + password, + + /** username generator using the EFF word list */ + effWordList, +}); // `CredentialAlgorithm` is defined in terms of `ABT`; supplying // type information in the barrel file breaks a circular dependency. @@ -12,14 +37,29 @@ export const AlgorithmsByType: Record< CredentialType, ReadonlyArray > = AlgorithmsByTypeData; + +/** A list of all built-in algorithm identifiers + * @remarks this is useful when you need to filter invalid values + */ export const Algorithms: ReadonlyArray = Object.freeze( Object.values(AlgorithmData), ); + +/** A list of all built-in algorithm types + * @remarks this is useful when you need to filter invalid values + */ export const Types: ReadonlyArray = Object.freeze(Object.values(TypeData)); export { Profile, Type, Algorithm } from "./data"; export { toForwarderMetadata } from "./email/forwarder"; +export { AlgorithmMetadata } from "./algorithm-metadata"; export { GeneratorMetadata } from "./generator-metadata"; export { ProfileContext, CoreProfileMetadata, ProfileMetadata } from "./profile-metadata"; -export { GeneratorProfile, CredentialAlgorithm, CredentialType } from "./type"; +export { + GeneratorProfile, + CredentialAlgorithm, + PasswordAlgorithm, + CredentialType, + ForwarderExtensionId, +} from "./type"; export { isForwarderProfile, toVendorId, isForwarderExtensionId } from "./util"; diff --git a/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts b/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts index e02d63d3d59..0c0693af272 100644 --- a/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts +++ b/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts @@ -5,7 +5,8 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { PasswordRandomizer } from "../../engine"; import { PassphrasePolicyConstraints } from "../../policies"; -import { PassphraseGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { PassphraseGenerationOptions } from "../../types"; import { Profile } from "../data"; import { CoreProfileMetadata } from "../profile-metadata"; import { isCoreProfile } from "../util"; diff --git a/libs/tools/generator/core/src/metadata/password/eff-word-list.ts b/libs/tools/generator/core/src/metadata/password/eff-word-list.ts index fc86032bf6b..021112f7c89 100644 --- a/libs/tools/generator/core/src/metadata/password/eff-word-list.ts +++ b/libs/tools/generator/core/src/metadata/password/eff-word-list.ts @@ -5,17 +5,14 @@ import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; import { PasswordRandomizer } from "../../engine"; import { passphraseLeastPrivilege, PassphrasePolicyConstraints } from "../../policies"; -import { - CredentialGenerator, - GeneratorDependencyProvider, - PassphraseGenerationOptions, -} from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CredentialGenerator, PassphraseGenerationOptions } from "../../types"; import { Algorithm, Profile, Type } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; const passphrase: GeneratorMetadata = { id: Algorithm.passphrase, - category: Type.password, + type: Type.password, weight: 110, i18nKeys: { name: "passphrase", @@ -26,7 +23,7 @@ const passphrase: GeneratorMetadata = { useCredential: "useThisPassphrase", }, capabilities: { - autogenerate: false, + autogenerate: true, fields: [], }, engine: { diff --git a/libs/tools/generator/core/src/metadata/password/random-password.spec.ts b/libs/tools/generator/core/src/metadata/password/random-password.spec.ts index 9e38c50ee2a..b22f3e9356d 100644 --- a/libs/tools/generator/core/src/metadata/password/random-password.spec.ts +++ b/libs/tools/generator/core/src/metadata/password/random-password.spec.ts @@ -5,7 +5,8 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { PasswordRandomizer } from "../../engine"; import { DynamicPasswordPolicyConstraints } from "../../policies"; -import { PasswordGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { PasswordGenerationOptions } from "../../types"; import { Profile } from "../data"; import { CoreProfileMetadata } from "../profile-metadata"; import { isCoreProfile } from "../util"; diff --git a/libs/tools/generator/core/src/metadata/password/random-password.ts b/libs/tools/generator/core/src/metadata/password/random-password.ts index 693236b0967..e446f1962a5 100644 --- a/libs/tools/generator/core/src/metadata/password/random-password.ts +++ b/libs/tools/generator/core/src/metadata/password/random-password.ts @@ -5,17 +5,14 @@ import { deepFreeze } from "@bitwarden/common/tools/util"; import { PasswordRandomizer } from "../../engine"; import { DynamicPasswordPolicyConstraints, passwordLeastPrivilege } from "../../policies"; -import { - CredentialGenerator, - GeneratorDependencyProvider, - PasswordGeneratorSettings, -} from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CredentialGenerator, PasswordGeneratorSettings } from "../../types"; import { Algorithm, Profile, Type } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; const password: GeneratorMetadata = deepFreeze({ id: Algorithm.password, - category: Type.password, + type: Type.password, weight: 100, i18nKeys: { name: "password", diff --git a/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts b/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts index d47d5ec9fcb..beebb016504 100644 --- a/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts +++ b/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts @@ -3,7 +3,8 @@ import { mock } from "jest-mock-extended"; import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint"; import { UsernameRandomizer } from "../../engine"; -import { EffUsernameGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { EffUsernameGenerationOptions } from "../../types"; import { Profile } from "../data"; import { CoreProfileMetadata } from "../profile-metadata"; import { isCoreProfile } from "../util"; diff --git a/libs/tools/generator/core/src/metadata/username/eff-word-list.ts b/libs/tools/generator/core/src/metadata/username/eff-word-list.ts index 6373daf8ed5..2802eea2c08 100644 --- a/libs/tools/generator/core/src/metadata/username/eff-word-list.ts +++ b/libs/tools/generator/core/src/metadata/username/eff-word-list.ts @@ -4,17 +4,14 @@ import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state import { deepFreeze } from "@bitwarden/common/tools/util"; import { UsernameRandomizer } from "../../engine"; -import { - CredentialGenerator, - EffUsernameGenerationOptions, - GeneratorDependencyProvider, -} from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CredentialGenerator, EffUsernameGenerationOptions } from "../../types"; import { Algorithm, Profile, Type } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; const effWordList: GeneratorMetadata = deepFreeze({ id: Algorithm.username, - category: Type.username, + type: Type.username, weight: 400, i18nKeys: { name: "randomWord", diff --git a/libs/tools/generator/core/src/metadata/util.ts b/libs/tools/generator/core/src/metadata/util.ts index 86b2742e86d..a8e8879b57c 100644 --- a/libs/tools/generator/core/src/metadata/util.ts +++ b/libs/tools/generator/core/src/metadata/util.ts @@ -12,23 +12,23 @@ import { /** Returns true when the input algorithm is a password algorithm. */ export function isPasswordAlgorithm( - algorithm: CredentialAlgorithm, + algorithm: CredentialAlgorithm | null, ): algorithm is PasswordAlgorithm { return AlgorithmsByType.password.includes(algorithm as any); } /** Returns true when the input algorithm is a username algorithm. */ export function isUsernameAlgorithm( - algorithm: CredentialAlgorithm, + algorithm: CredentialAlgorithm | null, ): algorithm is UsernameAlgorithm { return AlgorithmsByType.username.includes(algorithm as any); } /** Returns true when the input algorithm is a forwarder integration. */ export function isForwarderExtensionId( - algorithm: CredentialAlgorithm, + algorithm: CredentialAlgorithm | null, ): algorithm is ForwarderExtensionId { - return algorithm && typeof algorithm === "object" && "forwarder" in algorithm; + return !!(algorithm && typeof algorithm === "object" && "forwarder" in algorithm); } /** Extract a `VendorId` from a `CredentialAlgorithm`. diff --git a/libs/tools/generator/core/src/policies/available-algorithms-constraint.ts b/libs/tools/generator/core/src/policies/available-algorithms-constraint.ts new file mode 100644 index 00000000000..1824581664b --- /dev/null +++ b/libs/tools/generator/core/src/policies/available-algorithms-constraint.ts @@ -0,0 +1,76 @@ +import { SemanticLogger } from "@bitwarden/common/tools/log"; +import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider"; +import { Constraints, StateConstraints } from "@bitwarden/common/tools/types"; + +import { CredentialAlgorithm, CredentialType } from "../metadata"; +import { CredentialPreference } from "../types"; +import { TypeRequest } from "../types/metadata-request"; + +export class AvailableAlgorithmsConstraint implements StateConstraints { + /** Well-known constraints of `State` */ + readonly constraints: Readonly> = {}; + + /** Creates a password policy constraints + * @param algorithms loads the algorithms for an algorithm type + * @param isAvailable returns `true` when `algorithm` is enabled by policy + * @param system provides logging facilities + */ + constructor( + readonly algorithms: (request: TypeRequest) => CredentialAlgorithm[], + readonly isAvailable: (algorithm: CredentialAlgorithm) => boolean, + readonly system: UserStateSubjectDependencyProvider, + ) { + this.log = system.log({ type: "AvailableAlgorithmsConstraint" }); + } + private readonly log: SemanticLogger; + + adjust(preferences: CredentialPreference): CredentialPreference { + const result: any = {}; + + const types = Object.keys(preferences) as CredentialType[]; + for (const t of types) { + result[t] = this.adjustPreference(t, preferences[t]); + } + + return result; + } + + private adjustPreference(type: CredentialType, preference: { algorithm: CredentialAlgorithm }) { + if (this.isAvailable(preference.algorithm)) { + this.log.debug({ preference, type }, "using preferred algorithm"); + + return preference; + } + + // choose a default - this algorithm is arbitrary, but stable. + const algorithms = type ? this.algorithms({ type: type }) : []; + const defaultAlgorithm = algorithms.find(this.isAvailable) ?? null; + + // adjust the preference + let adjustedPreference; + if (defaultAlgorithm) { + adjustedPreference = { + ...preference, + algorithm: defaultAlgorithm, + updated: this.system.now(), + }; + this.log.debug( + { preference, defaultAlgorithm, type }, + "preference not available; defaulting the algorithm", + ); + } else { + // FIXME: hard-code a fallback in category metadata + this.log.warn( + { preference, type }, + "preference not available and default algorithm not found; continuing with preference", + ); + adjustedPreference = preference; + } + + return adjustedPreference; + } + + fix(preferences: CredentialPreference): CredentialPreference { + return preferences; + } +} diff --git a/libs/tools/generator/core/src/policies/available-algorithms-policy.spec.ts b/libs/tools/generator/core/src/policies/available-algorithms-policy.spec.ts index 1ef0adc1af4..5f699974fba 100644 --- a/libs/tools/generator/core/src/policies/available-algorithms-policy.spec.ts +++ b/libs/tools/generator/core/src/policies/available-algorithms-policy.spec.ts @@ -2,15 +2,15 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { PolicyId } from "@bitwarden/common/types/guid"; -import { CredentialAlgorithms, PasswordAlgorithms } from "../data"; +import { Algorithm, Algorithms, AlgorithmsByType } from "../metadata"; import { availableAlgorithms } from "./available-algorithms-policy"; -describe("availableAlgorithmsPolicy", () => { +describe("availableAlgorithms_vNextPolicy", () => { it("returns all algorithms", () => { const result = availableAlgorithms([]); - for (const expected of CredentialAlgorithms) { + for (const expected of Algorithms) { expect(result).toContain(expected); } }); @@ -30,7 +30,7 @@ describe("availableAlgorithmsPolicy", () => { expect(result).toContain(override); - for (const expected of PasswordAlgorithms.filter((a) => a !== override)) { + for (const expected of AlgorithmsByType[Algorithm.password].filter((a) => a !== override)) { expect(result).not.toContain(expected); } }); @@ -50,7 +50,7 @@ describe("availableAlgorithmsPolicy", () => { expect(result).toContain(override); - for (const expected of PasswordAlgorithms.filter((a) => a !== override)) { + for (const expected of AlgorithmsByType[Algorithm.password].filter((a) => a !== override)) { expect(result).not.toContain(expected); } }); @@ -79,7 +79,7 @@ describe("availableAlgorithmsPolicy", () => { expect(result).toContain("password"); - for (const expected of PasswordAlgorithms.filter((a) => a !== "password")) { + for (const expected of AlgorithmsByType[Algorithm.password].filter((a) => a !== "password")) { expect(result).not.toContain(expected); } }); @@ -97,7 +97,7 @@ describe("availableAlgorithmsPolicy", () => { const result = availableAlgorithms([policy]); - for (const expected of CredentialAlgorithms) { + for (const expected of Algorithms) { expect(result).toContain(expected); } }); @@ -115,7 +115,7 @@ describe("availableAlgorithmsPolicy", () => { const result = availableAlgorithms([policy]); - for (const expected of CredentialAlgorithms) { + for (const expected of Algorithms) { expect(result).toContain(expected); } }); @@ -133,7 +133,7 @@ describe("availableAlgorithmsPolicy", () => { const result = availableAlgorithms([policy]); - for (const expected of CredentialAlgorithms) { + for (const expected of Algorithms) { expect(result).toContain(expected); } }); diff --git a/libs/tools/generator/core/src/policies/available-algorithms-policy.ts b/libs/tools/generator/core/src/policies/available-algorithms-policy.ts index 0c44a1a0408..e63b648cf44 100644 --- a/libs/tools/generator/core/src/policies/available-algorithms-policy.ts +++ b/libs/tools/generator/core/src/policies/available-algorithms-policy.ts @@ -1,57 +1,30 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { PolicyType } from "@bitwarden/common/admin-console/enums"; // FIXME: use index.ts imports once policy abstractions and models // implement ADR-0002 import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { - CredentialAlgorithm as LegacyAlgorithm, - EmailAlgorithms, - PasswordAlgorithms, - UsernameAlgorithms, -} from ".."; -import { CredentialAlgorithm } from "../metadata"; +import { AlgorithmsByType, CredentialAlgorithm, Type } from "../metadata"; /** Reduces policies to a set of available algorithms * @param policies the policies to reduce * @returns the resulting `AlgorithmAvailabilityPolicy` */ -export function availableAlgorithms(policies: Policy[]): LegacyAlgorithm[] { +export function availableAlgorithms(policies: Policy[]): CredentialAlgorithm[] { const overridePassword = policies .filter((policy) => policy.type === PolicyType.PasswordGenerator && policy.enabled) .reduce( (type, policy) => (type === "password" ? type : (policy.data.overridePasswordType ?? type)), - null as LegacyAlgorithm, + null as CredentialAlgorithm | null, ); - const policy: LegacyAlgorithm[] = [...EmailAlgorithms, ...UsernameAlgorithms]; + const policy: CredentialAlgorithm[] = [ + ...AlgorithmsByType[Type.email], + ...AlgorithmsByType[Type.username], + ]; if (overridePassword) { policy.push(overridePassword); } else { - policy.push(...PasswordAlgorithms); - } - - return policy; -} - -/** Reduces policies to a set of available algorithms - * @param policies the policies to reduce - * @returns the resulting `AlgorithmAvailabilityPolicy` - */ -export function availableAlgorithms_vNext(policies: Policy[]): CredentialAlgorithm[] { - const overridePassword = policies - .filter((policy) => policy.type === PolicyType.PasswordGenerator && policy.enabled) - .reduce( - (type, policy) => (type === "password" ? type : (policy.data.overridePasswordType ?? type)), - null as CredentialAlgorithm, - ); - - const policy: CredentialAlgorithm[] = [...EmailAlgorithms, ...UsernameAlgorithms]; - if (overridePassword) { - policy.push(overridePassword); - } else { - policy.push(...PasswordAlgorithms); + policy.push(...AlgorithmsByType[Type.password]); } return policy; diff --git a/libs/tools/generator/core/src/policies/dynamic-password-policy-constraints.spec.ts b/libs/tools/generator/core/src/policies/dynamic-password-policy-constraints.spec.ts index c8ae02ef723..0bebb0825bf 100644 --- a/libs/tools/generator/core/src/policies/dynamic-password-policy-constraints.spec.ts +++ b/libs/tools/generator/core/src/policies/dynamic-password-policy-constraints.spec.ts @@ -1,15 +1,25 @@ import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; -import { Generators } from "../data"; +import { BuiltIn, Profile } from "../metadata"; import { PasswordGeneratorSettings } from "../types"; import { AtLeastOne, Zero } from "./constraints"; import { DynamicPasswordPolicyConstraints } from "./dynamic-password-policy-constraints"; -const accoutSettings = Generators.password.settings.account as ObjectKey; -const defaultOptions = accoutSettings.initial; -const disabledPolicy = Generators.password.policy.disabledValue; -const someConstraints = Generators.password.settings.constraints; +// non-null assertions used because these are always-defined constants +const accoutSettings = BuiltIn.password.profiles[Profile.account]! + .storage as ObjectKey; +const defaultOptions = accoutSettings.initial!; +const disabledPolicy = { + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, +}; +const someConstraints = BuiltIn.password.profiles[Profile.account]!.constraints.default; describe("DynamicPasswordPolicyConstraints", () => { describe("constructor", () => { @@ -33,8 +43,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const { constraints } = new DynamicPasswordPolicyConstraints(policy, someConstraints); expect(constraints.policyInEffect).toBeTruthy(); - expect(constraints.lowercase.readonly).toEqual(true); - expect(constraints.lowercase.requiredValue).toEqual(true); + expect(constraints.lowercase?.readonly).toEqual(true); + expect(constraints.lowercase?.requiredValue).toEqual(true); expect(constraints.minLowercase).toEqual({ min: 1 }); }); @@ -43,8 +53,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const { constraints } = new DynamicPasswordPolicyConstraints(policy, someConstraints); expect(constraints.policyInEffect).toBeTruthy(); - expect(constraints.uppercase.readonly).toEqual(true); - expect(constraints.uppercase.requiredValue).toEqual(true); + expect(constraints.uppercase?.readonly).toEqual(true); + expect(constraints.uppercase?.requiredValue).toEqual(true); expect(constraints.minUppercase).toEqual({ min: 1 }); }); @@ -53,8 +63,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const { constraints } = new DynamicPasswordPolicyConstraints(policy, someConstraints); expect(constraints.policyInEffect).toBeTruthy(); - expect(constraints.number.readonly).toEqual(true); - expect(constraints.number.requiredValue).toEqual(true); + expect(constraints.number?.readonly).toEqual(true); + expect(constraints.number?.requiredValue).toEqual(true); expect(constraints.minNumber).toEqual({ min: 1, max: 9 }); }); @@ -63,8 +73,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const { constraints } = new DynamicPasswordPolicyConstraints(policy, someConstraints); expect(constraints.policyInEffect).toBeTruthy(); - expect(constraints.special.readonly).toEqual(true); - expect(constraints.special.requiredValue).toEqual(true); + expect(constraints.special?.readonly).toEqual(true); + expect(constraints.special?.requiredValue).toEqual(true); expect(constraints.minSpecial).toEqual({ min: 1, max: 9 }); }); @@ -73,8 +83,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const { constraints } = new DynamicPasswordPolicyConstraints(policy, someConstraints); expect(constraints.policyInEffect).toBeTruthy(); - expect(constraints.number.readonly).toEqual(true); - expect(constraints.number.requiredValue).toEqual(true); + expect(constraints.number?.readonly).toEqual(true); + expect(constraints.number?.requiredValue).toEqual(true); expect(constraints.minNumber).toEqual({ min: 2, max: 9 }); }); @@ -83,8 +93,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const { constraints } = new DynamicPasswordPolicyConstraints(policy, someConstraints); expect(constraints.policyInEffect).toBeTruthy(); - expect(constraints.special.readonly).toEqual(true); - expect(constraints.special.requiredValue).toEqual(true); + expect(constraints.special?.readonly).toEqual(true); + expect(constraints.special?.requiredValue).toEqual(true); expect(constraints.minSpecial).toEqual({ min: 2, max: 9 }); }); @@ -140,7 +150,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const dynamic = new DynamicPasswordPolicyConstraints( { ...disabledPolicy, - useLowercase, + // the `undefined` case is testing behavior when the type system is bypassed + useLowercase: useLowercase!, }, someConstraints, ); @@ -185,7 +196,8 @@ describe("DynamicPasswordPolicyConstraints", () => { const dynamic = new DynamicPasswordPolicyConstraints( { ...disabledPolicy, - useUppercase, + // the `undefined` case is testing behavior when the type system is bypassed + useUppercase: useUppercase!, }, someConstraints, ); diff --git a/libs/tools/generator/core/src/policies/index.ts b/libs/tools/generator/core/src/policies/index.ts index 0d05e702306..893f0402d38 100644 --- a/libs/tools/generator/core/src/policies/index.ts +++ b/libs/tools/generator/core/src/policies/index.ts @@ -5,3 +5,5 @@ export { PassphrasePolicyConstraints } from "./passphrase-policy-constraints"; export { PasswordGeneratorOptionsEvaluator } from "./password-generator-options-evaluator"; export { passphraseLeastPrivilege } from "./passphrase-least-privilege"; export { passwordLeastPrivilege } from "./password-least-privilege"; +export { AvailableAlgorithmsConstraint } from "./available-algorithms-constraint"; +export { availableAlgorithms } from "./available-algorithms-policy"; diff --git a/libs/tools/generator/core/src/policies/passphrase-generator-options-evaluator.spec.ts b/libs/tools/generator/core/src/policies/passphrase-generator-options-evaluator.spec.ts index 3b1eb799391..5fcf847c504 100644 --- a/libs/tools/generator/core/src/policies/passphrase-generator-options-evaluator.spec.ts +++ b/libs/tools/generator/core/src/policies/passphrase-generator-options-evaluator.spec.ts @@ -1,12 +1,32 @@ -import { Policies, DefaultPassphraseBoundaries } from "../data"; -import { PassphraseGenerationOptions } from "../types"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { deepFreeze } from "@bitwarden/common/tools/util"; + +import { DefaultPassphraseBoundaries } from "../data"; +import { + PassphraseGenerationOptions, + PassphraseGeneratorPolicy, + PolicyConfiguration, +} from "../types"; import { PassphraseGeneratorOptionsEvaluator } from "./passphrase-generator-options-evaluator"; +import { passphraseLeastPrivilege } from "./passphrase-least-privilege"; -describe("Password generator options builder", () => { +const Passphrase: PolicyConfiguration = + deepFreeze({ + type: PolicyType.PasswordGenerator, + disabledValue: { + minNumberWords: 0, + capitalize: false, + includeNumber: false, + }, + combine: passphraseLeastPrivilege, + createEvaluator: (policy) => new PassphraseGeneratorOptionsEvaluator(policy), + }); + +describe("Passphrase generator options builder", () => { describe("constructor()", () => { it("should set the policy object to a copy of the input policy", () => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.minNumberWords = 10; // arbitrary change for deep equality check const builder = new PassphraseGeneratorOptionsEvaluator(policy); @@ -16,7 +36,7 @@ describe("Password generator options builder", () => { }); it("should set default boundaries when a default policy is used", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); expect(builder.numWords).toEqual(DefaultPassphraseBoundaries.numWords); @@ -25,7 +45,7 @@ describe("Password generator options builder", () => { it.each([1, 2])( "should use the default word boundaries when they are greater than `policy.minNumberWords` (= %i)", (minNumberWords) => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.minNumberWords = minNumberWords; const builder = new PassphraseGeneratorOptionsEvaluator(policy); @@ -37,7 +57,7 @@ describe("Password generator options builder", () => { it.each([8, 12, 18])( "should use `policy.minNumberWords` (= %i) when it is greater than the default minimum words", (minNumberWords) => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.minNumberWords = minNumberWords; const builder = new PassphraseGeneratorOptionsEvaluator(policy); @@ -50,7 +70,7 @@ describe("Password generator options builder", () => { it.each([150, 300, 9000])( "should use `policy.minNumberWords` (= %i) when it is greater than the default boundaries", (minNumberWords) => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.minNumberWords = minNumberWords; const builder = new PassphraseGeneratorOptionsEvaluator(policy); @@ -63,14 +83,14 @@ describe("Password generator options builder", () => { describe("policyInEffect", () => { it("should return false when the policy has no effect", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); expect(builder.policyInEffect).toEqual(false); }); it("should return true when the policy has a numWords greater than the default boundary", () => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.minNumberWords = DefaultPassphraseBoundaries.numWords.min + 1; const builder = new PassphraseGeneratorOptionsEvaluator(policy); @@ -78,7 +98,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has capitalize enabled", () => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.capitalize = true; const builder = new PassphraseGeneratorOptionsEvaluator(policy); @@ -86,7 +106,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has includeNumber enabled", () => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.includeNumber = true; const builder = new PassphraseGeneratorOptionsEvaluator(policy); @@ -98,7 +118,7 @@ describe("Password generator options builder", () => { // All tests should freeze the options to ensure they are not modified it("should set `capitalize` to `false` when the policy does not override it", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({}); @@ -108,7 +128,7 @@ describe("Password generator options builder", () => { }); it("should set `capitalize` to `true` when the policy overrides it", () => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.capitalize = true; const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ capitalize: false }); @@ -119,7 +139,7 @@ describe("Password generator options builder", () => { }); it("should set `includeNumber` to false when the policy does not override it", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({}); @@ -129,7 +149,7 @@ describe("Password generator options builder", () => { }); it("should set `includeNumber` to true when the policy overrides it", () => { - const policy: any = Object.assign({}, Policies.Passphrase.disabledValue); + const policy: any = Object.assign({}, Passphrase.disabledValue); policy.includeNumber = true; const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ includeNumber: false }); @@ -140,7 +160,7 @@ describe("Password generator options builder", () => { }); it("should set `numWords` to the minimum value when it isn't supplied", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({}); @@ -154,7 +174,7 @@ describe("Password generator options builder", () => { (numWords) => { expect(numWords).toBeLessThan(DefaultPassphraseBoundaries.numWords.min); - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ numWords }); @@ -170,7 +190,7 @@ describe("Password generator options builder", () => { expect(numWords).toBeGreaterThanOrEqual(DefaultPassphraseBoundaries.numWords.min); expect(numWords).toBeLessThanOrEqual(DefaultPassphraseBoundaries.numWords.max); - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ numWords }); @@ -185,7 +205,7 @@ describe("Password generator options builder", () => { (numWords) => { expect(numWords).toBeGreaterThan(DefaultPassphraseBoundaries.numWords.max); - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ numWords }); @@ -196,7 +216,7 @@ describe("Password generator options builder", () => { ); it("should preserve unknown properties", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ unknown: "property", @@ -214,7 +234,7 @@ describe("Password generator options builder", () => { // All tests should freeze the options to ensure they are not modified it("should return the input options without altering them", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ wordSeparator: "%" }); @@ -224,7 +244,7 @@ describe("Password generator options builder", () => { }); it("should set `wordSeparator` to '-' when it isn't supplied and there is no policy override", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({}); @@ -234,7 +254,7 @@ describe("Password generator options builder", () => { }); it("should leave `wordSeparator` as the empty string '' when it is the empty string", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ wordSeparator: "" }); @@ -244,7 +264,7 @@ describe("Password generator options builder", () => { }); it("should preserve unknown properties", () => { - const policy = Object.assign({}, Policies.Passphrase.disabledValue); + const policy = Object.assign({}, Passphrase.disabledValue); const builder = new PassphraseGeneratorOptionsEvaluator(policy); const options = Object.freeze({ unknown: "property", diff --git a/libs/tools/generator/core/src/policies/passphrase-least-privilege.spec.ts b/libs/tools/generator/core/src/policies/passphrase-least-privilege.spec.ts index ecac3855987..0fbc1796e9e 100644 --- a/libs/tools/generator/core/src/policies/passphrase-least-privilege.spec.ts +++ b/libs/tools/generator/core/src/policies/passphrase-least-privilege.spec.ts @@ -4,8 +4,6 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { PolicyId } from "@bitwarden/common/types/guid"; -import { Policies } from "../data"; - import { passphraseLeastPrivilege } from "./passphrase-least-privilege"; function createPolicy( @@ -22,21 +20,27 @@ function createPolicy( }); } +const disabledValue = Object.freeze({ + minNumberWords: 0, + capitalize: false, + includeNumber: false, +}); + describe("passphraseLeastPrivilege", () => { it("should return the accumulator when the policy type does not apply", () => { const policy = createPolicy({}, PolicyType.RequireSso); - const result = passphraseLeastPrivilege(Policies.Passphrase.disabledValue, policy); + const result = passphraseLeastPrivilege(disabledValue, policy); - expect(result).toEqual(Policies.Passphrase.disabledValue); + expect(result).toEqual(disabledValue); }); it("should return the accumulator when the policy is not enabled", () => { const policy = createPolicy({}, PolicyType.PasswordGenerator, false); - const result = passphraseLeastPrivilege(Policies.Passphrase.disabledValue, policy); + const result = passphraseLeastPrivilege(disabledValue, policy); - expect(result).toEqual(Policies.Passphrase.disabledValue); + expect(result).toEqual(disabledValue); }); it.each([ @@ -46,8 +50,8 @@ describe("passphraseLeastPrivilege", () => { ])("should take the %p from the policy", (input, value) => { const policy = createPolicy({ [input]: value }); - const result = passphraseLeastPrivilege(Policies.Passphrase.disabledValue, policy); + const result = passphraseLeastPrivilege(disabledValue, policy); - expect(result).toEqual({ ...Policies.Passphrase.disabledValue, [input]: value }); + expect(result).toEqual({ ...disabledValue, [input]: value }); }); }); diff --git a/libs/tools/generator/core/src/policies/passphrase-policy-constraints.spec.ts b/libs/tools/generator/core/src/policies/passphrase-policy-constraints.spec.ts index d6e0a5615dc..6306382c84e 100644 --- a/libs/tools/generator/core/src/policies/passphrase-policy-constraints.spec.ts +++ b/libs/tools/generator/core/src/policies/passphrase-policy-constraints.spec.ts @@ -1,4 +1,4 @@ -import { Generators } from "../data"; +import { BuiltIn, Profile } from "../metadata"; import { PassphrasePolicyConstraints } from "./passphrase-policy-constraints"; @@ -9,8 +9,12 @@ const SomeSettings = { wordSeparator: "-", }; -const disabledPolicy = Generators.passphrase.policy.disabledValue; -const someConstraints = Generators.passphrase.settings.constraints; +const disabledPolicy = { + minNumberWords: 0, + capitalize: false, + includeNumber: false, +}; +const someConstraints = BuiltIn.passphrase.profiles[Profile.account]!.constraints.default; describe("PassphrasePolicyConstraints", () => { describe("constructor", () => { @@ -61,7 +65,7 @@ describe("PassphrasePolicyConstraints", () => { expect(constraints.policyInEffect).toBeTruthy(); expect(constraints.numWords).toMatchObject({ min: 10, - max: someConstraints.numWords.max, + max: someConstraints.numWords?.max, }); }); }); @@ -84,8 +88,8 @@ describe("PassphrasePolicyConstraints", () => { }); it.each([ - [1, someConstraints.numWords.min, 3, someConstraints.numWords.max], - [21, someConstraints.numWords.min, 20, someConstraints.numWords.max], + [1, someConstraints.numWords?.min, 3, someConstraints.numWords?.max], + [21, someConstraints.numWords?.min, 20, someConstraints.numWords?.max], ])( `fits numWords (=%p) within the default bounds (%p <= %p <= %p)`, (value, _, expected, __) => { @@ -98,8 +102,8 @@ describe("PassphrasePolicyConstraints", () => { ); it.each([ - [1, 6, 6, someConstraints.numWords.max], - [21, 20, 20, someConstraints.numWords.max], + [1, 6, 6, someConstraints.numWords?.max], + [21, 20, 20, someConstraints.numWords?.max], ])( "fits numWords (=%p) within the policy bounds (%p <= %p <= %p)", (value, minNumberWords, expected, _) => { diff --git a/libs/tools/generator/core/src/policies/password-generator-options-evaluator.spec.ts b/libs/tools/generator/core/src/policies/password-generator-options-evaluator.spec.ts index 91334f91f85..a088f93d3fe 100644 --- a/libs/tools/generator/core/src/policies/password-generator-options-evaluator.spec.ts +++ b/libs/tools/generator/core/src/policies/password-generator-options-evaluator.spec.ts @@ -1,14 +1,34 @@ -import { DefaultPasswordBoundaries, Policies } from "../data"; -import { PasswordGenerationOptions } from "../types"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { deepFreeze } from "@bitwarden/common/tools/util"; + +import { DefaultPasswordBoundaries } from "../data"; +import { PasswordGenerationOptions, PasswordGeneratorPolicy, PolicyConfiguration } from "../types"; import { PasswordGeneratorOptionsEvaluator } from "./password-generator-options-evaluator"; +import { passwordLeastPrivilege } from "./password-least-privilege"; + +const Password: PolicyConfiguration = + deepFreeze({ + type: PolicyType.PasswordGenerator, + disabledValue: { + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, + }, + combine: passwordLeastPrivilege, + createEvaluator: (policy) => new PasswordGeneratorOptionsEvaluator(policy), + }); describe("Password generator options builder", () => { const defaultOptions = Object.freeze({ minLength: 0 }); describe("constructor()", () => { it("should set the policy object to a copy of the input policy", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.minLength = 10; // arbitrary change for deep equality check const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -18,7 +38,7 @@ describe("Password generator options builder", () => { }); it("should set default boundaries when a default policy is used", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -32,7 +52,7 @@ describe("Password generator options builder", () => { (minLength) => { expect(minLength).toBeLessThan(DefaultPasswordBoundaries.length.min); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.minLength = minLength; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -47,7 +67,7 @@ describe("Password generator options builder", () => { expect(expectedLength).toBeGreaterThan(DefaultPasswordBoundaries.length.min); expect(expectedLength).toBeLessThanOrEqual(DefaultPasswordBoundaries.length.max); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.minLength = expectedLength; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -62,7 +82,7 @@ describe("Password generator options builder", () => { (expectedLength) => { expect(expectedLength).toBeGreaterThan(DefaultPasswordBoundaries.length.max); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.minLength = expectedLength; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -78,7 +98,7 @@ describe("Password generator options builder", () => { expect(expectedMinDigits).toBeGreaterThan(DefaultPasswordBoundaries.minDigits.min); expect(expectedMinDigits).toBeLessThanOrEqual(DefaultPasswordBoundaries.minDigits.max); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.numberCount = expectedMinDigits; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -93,7 +113,7 @@ describe("Password generator options builder", () => { (expectedMinDigits) => { expect(expectedMinDigits).toBeGreaterThan(DefaultPasswordBoundaries.minDigits.max); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.numberCount = expectedMinDigits; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -113,7 +133,7 @@ describe("Password generator options builder", () => { DefaultPasswordBoundaries.minSpecialCharacters.max, ); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.specialCount = expectedSpecialCharacters; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -132,7 +152,7 @@ describe("Password generator options builder", () => { DefaultPasswordBoundaries.minSpecialCharacters.max, ); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.specialCount = expectedSpecialCharacters; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -151,7 +171,7 @@ describe("Password generator options builder", () => { (expectedLength, numberCount, specialCount) => { expect(expectedLength).toBeGreaterThanOrEqual(DefaultPasswordBoundaries.length.min); - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.numberCount = numberCount; policy.specialCount = specialCount; @@ -164,14 +184,14 @@ describe("Password generator options builder", () => { describe("policyInEffect", () => { it("should return false when the policy has no effect", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(builder.policyInEffect).toEqual(false); }); it("should return true when the policy has a minlength greater than the default boundary", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.minLength = DefaultPasswordBoundaries.length.min + 1; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -179,7 +199,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has a number count greater than the default boundary", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.numberCount = DefaultPasswordBoundaries.minDigits.min + 1; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -187,7 +207,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has a special character count greater than the default boundary", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.specialCount = DefaultPasswordBoundaries.minSpecialCharacters.min + 1; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -195,7 +215,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has uppercase enabled", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useUppercase = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -203,7 +223,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has lowercase enabled", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useLowercase = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -211,7 +231,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has numbers enabled", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useNumbers = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -219,7 +239,7 @@ describe("Password generator options builder", () => { }); it("should return true when the policy has special characters enabled", () => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useSpecial = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); @@ -237,7 +257,7 @@ describe("Password generator options builder", () => { ])( "should set `options.uppercase` to '%s' when `policy.useUppercase` is false and `options.uppercase` is '%s'", (expectedUppercase, uppercase) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useUppercase = false; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, uppercase }); @@ -251,7 +271,7 @@ describe("Password generator options builder", () => { it.each([false, true, undefined])( "should set `options.uppercase` (= %s) to true when `policy.useUppercase` is true", (uppercase) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useUppercase = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, uppercase }); @@ -269,7 +289,7 @@ describe("Password generator options builder", () => { ])( "should set `options.lowercase` to '%s' when `policy.useLowercase` is false and `options.lowercase` is '%s'", (expectedLowercase, lowercase) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useLowercase = false; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, lowercase }); @@ -283,7 +303,7 @@ describe("Password generator options builder", () => { it.each([false, true, undefined])( "should set `options.lowercase` (= %s) to true when `policy.useLowercase` is true", (lowercase) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useLowercase = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, lowercase }); @@ -301,7 +321,7 @@ describe("Password generator options builder", () => { ])( "should set `options.number` to '%s' when `policy.useNumbers` is false and `options.number` is '%s'", (expectedNumber, number) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useNumbers = false; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, number }); @@ -315,7 +335,7 @@ describe("Password generator options builder", () => { it.each([false, true, undefined])( "should set `options.number` (= %s) to true when `policy.useNumbers` is true", (number) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useNumbers = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, number }); @@ -333,7 +353,7 @@ describe("Password generator options builder", () => { ])( "should set `options.special` to '%s' when `policy.useSpecial` is false and `options.special` is '%s'", (expectedSpecial, special) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useSpecial = false; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, special }); @@ -347,7 +367,7 @@ describe("Password generator options builder", () => { it.each([false, true, undefined])( "should set `options.special` (= %s) to true when `policy.useSpecial` is true", (special) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.useSpecial = true; const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, special }); @@ -361,7 +381,7 @@ describe("Password generator options builder", () => { it.each([1, 2, 3, 4])( "should set `options.length` (= %i) to the minimum it is less than the minimum length", (length) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(length).toBeLessThan(builder.length.min); @@ -376,7 +396,7 @@ describe("Password generator options builder", () => { it.each([5, 10, 50, 100, 128])( "should not change `options.length` (= %i) when it is within the boundaries", (length) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(length).toBeGreaterThanOrEqual(builder.length.min); expect(length).toBeLessThanOrEqual(builder.length.max); @@ -392,7 +412,7 @@ describe("Password generator options builder", () => { it.each([129, 500, 9000])( "should set `options.length` (= %i) to the maximum length when it is exceeded", (length) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(length).toBeGreaterThan(builder.length.max); @@ -414,7 +434,7 @@ describe("Password generator options builder", () => { ])( "should set `options.number === %s` when `options.minNumber` (= %i) is set to a value greater than 0", (expectedNumber, minNumber) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, minNumber }); @@ -425,7 +445,7 @@ describe("Password generator options builder", () => { ); it("should set `options.minNumber` to the minimum value when `options.number` is true", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, number: true }); @@ -435,7 +455,7 @@ describe("Password generator options builder", () => { }); it("should set `options.minNumber` to 0 when `options.number` is false", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, number: false }); @@ -447,7 +467,7 @@ describe("Password generator options builder", () => { it.each([1, 2, 3, 4])( "should set `options.minNumber` (= %i) to the minimum it is less than the minimum number", (minNumber) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.numberCount = 5; // arbitrary value greater than minNumber expect(minNumber).toBeLessThan(policy.numberCount); @@ -463,7 +483,7 @@ describe("Password generator options builder", () => { it.each([1, 3, 5, 7, 9])( "should not change `options.minNumber` (= %i) when it is within the boundaries", (minNumber) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(minNumber).toBeGreaterThanOrEqual(builder.minDigits.min); expect(minNumber).toBeLessThanOrEqual(builder.minDigits.max); @@ -479,7 +499,7 @@ describe("Password generator options builder", () => { it.each([10, 20, 400])( "should set `options.minNumber` (= %i) to the maximum digit boundary when it is exceeded", (minNumber) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(minNumber).toBeGreaterThan(builder.minDigits.max); @@ -501,7 +521,7 @@ describe("Password generator options builder", () => { ])( "should set `options.special === %s` when `options.minSpecial` (= %i) is set to a value greater than 0", (expectedSpecial, minSpecial) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, minSpecial }); @@ -512,7 +532,7 @@ describe("Password generator options builder", () => { ); it("should set `options.minSpecial` to the minimum value when `options.special` is true", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, special: true }); @@ -522,7 +542,7 @@ describe("Password generator options builder", () => { }); it("should set `options.minSpecial` to 0 when `options.special` is false", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ ...defaultOptions, special: false }); @@ -534,7 +554,7 @@ describe("Password generator options builder", () => { it.each([1, 2, 3, 4])( "should set `options.minSpecial` (= %i) to the minimum it is less than the minimum special characters", (minSpecial) => { - const policy: any = Object.assign({}, Policies.Password.disabledValue); + const policy: any = Object.assign({}, Password.disabledValue); policy.specialCount = 5; // arbitrary value greater than minSpecial expect(minSpecial).toBeLessThan(policy.specialCount); @@ -550,7 +570,7 @@ describe("Password generator options builder", () => { it.each([1, 3, 5, 7, 9])( "should not change `options.minSpecial` (= %i) when it is within the boundaries", (minSpecial) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(minSpecial).toBeGreaterThanOrEqual(builder.minSpecialCharacters.min); expect(minSpecial).toBeLessThanOrEqual(builder.minSpecialCharacters.max); @@ -566,7 +586,7 @@ describe("Password generator options builder", () => { it.each([10, 20, 400])( "should set `options.minSpecial` (= %i) to the maximum special character boundary when it is exceeded", (minSpecial) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); expect(minSpecial).toBeGreaterThan(builder.minSpecialCharacters.max); @@ -579,7 +599,7 @@ describe("Password generator options builder", () => { ); it("should preserve unknown properties", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ unknown: "property", @@ -602,7 +622,7 @@ describe("Password generator options builder", () => { ])( "should output `options.minLowercase === %i` when `options.lowercase` is %s", (expectedMinLowercase, lowercase) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ lowercase, ...defaultOptions }); @@ -618,7 +638,7 @@ describe("Password generator options builder", () => { ])( "should output `options.minUppercase === %i` when `options.uppercase` is %s", (expectedMinUppercase, uppercase) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ uppercase, ...defaultOptions }); @@ -634,7 +654,7 @@ describe("Password generator options builder", () => { ])( "should output `options.minNumber === %i` when `options.number` is %s and `options.minNumber` is not set", (expectedMinNumber, number) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ number, ...defaultOptions }); @@ -652,7 +672,7 @@ describe("Password generator options builder", () => { ])( "should output `options.number === %s` when `options.minNumber` is %i and `options.number` is not set", (expectedNumber, minNumber) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ minNumber, ...defaultOptions }); @@ -668,7 +688,7 @@ describe("Password generator options builder", () => { ])( "should output `options.minSpecial === %i` when `options.special` is %s and `options.minSpecial` is not set", (special, expectedMinSpecial) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ special, ...defaultOptions }); @@ -686,7 +706,7 @@ describe("Password generator options builder", () => { ])( "should output `options.special === %s` when `options.minSpecial` is %i and `options.special` is not set", (minSpecial, expectedSpecial) => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ minSpecial, ...defaultOptions }); @@ -707,7 +727,7 @@ describe("Password generator options builder", () => { const sumOfMinimums = minLowercase + minUppercase + minNumber + minSpecial; expect(sumOfMinimums).toBeLessThan(DefaultPasswordBoundaries.length.min); - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ minLowercase, @@ -732,7 +752,7 @@ describe("Password generator options builder", () => { (expectedMinLength, minLowercase, minUppercase, minNumber, minSpecial) => { expect(expectedMinLength).toBeGreaterThanOrEqual(DefaultPasswordBoundaries.length.min); - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ minLowercase, @@ -749,7 +769,7 @@ describe("Password generator options builder", () => { ); it("should preserve unknown properties", () => { - const policy = Object.assign({}, Policies.Password.disabledValue); + const policy = Object.assign({}, Password.disabledValue); const builder = new PasswordGeneratorOptionsEvaluator(policy); const options = Object.freeze({ unknown: "property", diff --git a/libs/tools/generator/core/src/policies/password-least-privilege.spec.ts b/libs/tools/generator/core/src/policies/password-least-privilege.spec.ts index 5d5430b8cad..7f8dce19b15 100644 --- a/libs/tools/generator/core/src/policies/password-least-privilege.spec.ts +++ b/libs/tools/generator/core/src/policies/password-least-privilege.spec.ts @@ -4,8 +4,6 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { PolicyId } from "@bitwarden/common/types/guid"; -import { Policies } from "../data"; - import { passwordLeastPrivilege } from "./password-least-privilege"; function createPolicy( @@ -22,21 +20,31 @@ function createPolicy( }); } +const disabledValue = Object.freeze({ + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, +}); + describe("passwordLeastPrivilege", () => { it("should return the accumulator when the policy type does not apply", () => { const policy = createPolicy({}, PolicyType.RequireSso); - const result = passwordLeastPrivilege(Policies.Password.disabledValue, policy); + const result = passwordLeastPrivilege(disabledValue, policy); - expect(result).toEqual(Policies.Password.disabledValue); + expect(result).toEqual(disabledValue); }); it("should return the accumulator when the policy is not enabled", () => { const policy = createPolicy({}, PolicyType.PasswordGenerator, false); - const result = passwordLeastPrivilege(Policies.Password.disabledValue, policy); + const result = passwordLeastPrivilege(disabledValue, policy); - expect(result).toEqual(Policies.Password.disabledValue); + expect(result).toEqual(disabledValue); }); it.each([ @@ -50,8 +58,8 @@ describe("passwordLeastPrivilege", () => { ])("should take the %p from the policy", (input, value, expected) => { const policy = createPolicy({ [input]: value }); - const result = passwordLeastPrivilege(Policies.Password.disabledValue, policy); + const result = passwordLeastPrivilege(disabledValue, policy); - expect(result).toEqual({ ...Policies.Password.disabledValue, [expected]: value }); + expect(result).toEqual({ ...disabledValue, [expected]: value }); }); }); diff --git a/libs/tools/generator/core/src/providers/credential-generator-providers.ts b/libs/tools/generator/core/src/providers/credential-generator-providers.ts new file mode 100644 index 00000000000..1e1a8345f34 --- /dev/null +++ b/libs/tools/generator/core/src/providers/credential-generator-providers.ts @@ -0,0 +1,14 @@ +import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider"; + +import { GeneratorDependencyProvider } from "./generator-dependency-provider"; +import { GeneratorMetadataProvider } from "./generator-metadata-provider"; +import { GeneratorProfileProvider } from "./generator-profile-provider"; + +// FIXME: find a better way to manage common dependencies than smashing them all +// together into a mega-type. +export type CredentialGeneratorProviders = { + readonly userState: UserStateSubjectDependencyProvider; + readonly generator: GeneratorDependencyProvider; + readonly profile: GeneratorProfileProvider; + readonly metadata: GeneratorMetadataProvider; +}; diff --git a/libs/tools/generator/core/src/providers/credential-preferences.spec.ts b/libs/tools/generator/core/src/providers/credential-preferences.spec.ts new file mode 100644 index 00000000000..6fd747f3823 --- /dev/null +++ b/libs/tools/generator/core/src/providers/credential-preferences.spec.ts @@ -0,0 +1,105 @@ +import { AlgorithmsByType, Type } from "../metadata"; +import { CredentialPreference } from "../types"; + +import { PREFERENCES } from "./credential-preferences"; + +const SomeCredentialPreferences: CredentialPreference = Object.freeze({ + email: Object.freeze({ + algorithm: AlgorithmsByType[Type.email][0], + updated: new Date(0), + }), + password: Object.freeze({ + algorithm: AlgorithmsByType[Type.password][0], + updated: new Date(0), + }), + username: Object.freeze({ + algorithm: AlgorithmsByType[Type.username][0], + updated: new Date(0), + }), +}); + +describe("PREFERENCES", () => { + describe("deserializer", () => { + it.each([[null], [undefined]])("creates new preferences (= %p)", (value) => { + // this case tests what happens when the type system is bypassed + const result = PREFERENCES.deserializer(value!); + + expect(result).toMatchObject({ + email: { + algorithm: AlgorithmsByType[Type.email][0], + }, + password: { + algorithm: AlgorithmsByType[Type.password][0], + }, + username: { + algorithm: AlgorithmsByType[Type.username][0], + }, + }); + }); + + it("fills missing password preferences", () => { + const input: any = structuredClone(SomeCredentialPreferences); + delete input.password; + + const result = PREFERENCES.deserializer(input); + + expect(result).toMatchObject({ + password: { + algorithm: AlgorithmsByType[Type.password][0], + }, + }); + }); + + it("fills missing email preferences", () => { + const input: any = structuredClone(SomeCredentialPreferences); + delete input.email; + + const result = PREFERENCES.deserializer(input); + + expect(result).toMatchObject({ + email: { + algorithm: AlgorithmsByType[Type.email][0], + }, + }); + }); + + it("fills missing username preferences", () => { + const input: any = structuredClone(SomeCredentialPreferences); + delete input.username; + + const result = PREFERENCES.deserializer(input); + + expect(result).toMatchObject({ + username: { + algorithm: AlgorithmsByType[Type.username][0], + }, + }); + }); + + it("converts string fields to Dates", () => { + const input: any = structuredClone(SomeCredentialPreferences); + input.email.updated = "1970-01-01T00:00:00.100Z"; + input.password.updated = "1970-01-01T00:00:00.200Z"; + input.username.updated = "1970-01-01T00:00:00.300Z"; + + const result = PREFERENCES.deserializer(input); + + expect(result?.email.updated).toEqual(new Date(100)); + expect(result?.password.updated).toEqual(new Date(200)); + expect(result?.username.updated).toEqual(new Date(300)); + }); + + it("converts number fields to Dates", () => { + const input: any = structuredClone(SomeCredentialPreferences); + input.email.updated = 100; + input.password.updated = 200; + input.username.updated = 300; + + const result = PREFERENCES.deserializer(input); + + expect(result?.email.updated).toEqual(new Date(100)); + expect(result?.password.updated).toEqual(new Date(200)); + expect(result?.username.updated).toEqual(new Date(300)); + }); + }); +}); diff --git a/libs/tools/generator/core/src/providers/credential-preferences.ts b/libs/tools/generator/core/src/providers/credential-preferences.ts new file mode 100644 index 00000000000..5c6efd6008b --- /dev/null +++ b/libs/tools/generator/core/src/providers/credential-preferences.ts @@ -0,0 +1,28 @@ +import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state"; + +import { AlgorithmsByType, CredentialType } from "../metadata"; +import { CredentialPreference } from "../types"; + +/** plaintext password generation options */ +export const PREFERENCES = new UserKeyDefinition( + GENERATOR_DISK, + "credentialPreferences", + { + deserializer: (value) => { + const result = (value as any) ?? {}; + + for (const key in AlgorithmsByType) { + const type = key as CredentialType; + if (result[type]) { + result[type].updated = new Date(result[type].updated); + } else { + const [algorithm] = AlgorithmsByType[type]; + result[type] = { algorithm, updated: new Date() }; + } + } + + return result; + }, + clearOn: ["logout"], + }, +); diff --git a/libs/tools/generator/core/src/providers/generator-dependency-provider.ts b/libs/tools/generator/core/src/providers/generator-dependency-provider.ts new file mode 100644 index 00000000000..14942698cdb --- /dev/null +++ b/libs/tools/generator/core/src/providers/generator-dependency-provider.ts @@ -0,0 +1,12 @@ +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { RestClient } from "@bitwarden/common/tools/integration/rpc"; + +import { Randomizer } from "../abstractions"; + +export type GeneratorDependencyProvider = { + randomizer: Randomizer; + client: RestClient; + // FIXME: introduce `I18nKeyOrLiteral` into forwarder + // structures and remove this dependency + i18nService: I18nService; +}; diff --git a/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts b/libs/tools/generator/core/src/providers/generator-metadata-provider.spec.ts similarity index 98% rename from libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts rename to libs/tools/generator/core/src/providers/generator-metadata-provider.spec.ts index 37a987f88bc..71fced46fa6 100644 --- a/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts +++ b/libs/tools/generator/core/src/providers/generator-metadata-provider.spec.ts @@ -75,6 +75,7 @@ const SystemProvider = { } as LegacyEncryptorProvider, state: SomeStateProvider, log: disabledSemanticLoggerProvider, + now: Date.now, } as UserStateSubjectDependencyProvider; const SomeSiteId: SiteId = Site.forwarder; @@ -415,14 +416,14 @@ describe("GeneratorMetadataProvider", () => { await expect(firstValueFrom(result)).resolves.toEqual(plusAddress.id); }); - it("emits undefined when the user's preference is unavailable and there is no metadata", async () => { + it("emits the original preference when the user's preference is unavailable and there is no metadata", async () => { SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([])); const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []); const result = new ReplaySubject(1); provider.preference$(Type.email, { account$: SomeAccount$ }).subscribe(result); - await expect(firstValueFrom(result)).resolves.toBeUndefined(); + await expect(firstValueFrom(result)).resolves.toEqual(preferences[Type.email].algorithm); }); }); diff --git a/libs/tools/generator/core/src/services/generator-metadata-provider.ts b/libs/tools/generator/core/src/providers/generator-metadata-provider.ts similarity index 87% rename from libs/tools/generator/core/src/services/generator-metadata-provider.ts rename to libs/tools/generator/core/src/providers/generator-metadata-provider.ts index 161f7192c39..52901545023 100644 --- a/libs/tools/generator/core/src/services/generator-metadata-provider.ts +++ b/libs/tools/generator/core/src/providers/generator-metadata-provider.ts @@ -1,12 +1,4 @@ -import { - Observable, - combineLatestWith, - distinctUntilChanged, - map, - shareReplay, - switchMap, - takeUntil, -} from "rxjs"; +import { Observable, distinctUntilChanged, map, shareReplay, switchMap, takeUntil } from "rxjs"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; @@ -14,7 +6,7 @@ import { BoundDependency } from "@bitwarden/common/tools/dependencies"; import { ExtensionSite } from "@bitwarden/common/tools/extension"; import { SemanticLogger } from "@bitwarden/common/tools/log"; import { SystemServiceProvider } from "@bitwarden/common/tools/providers"; -import { anyComplete, pin } from "@bitwarden/common/tools/rx"; +import { anyComplete, memoizedMap, pin } from "@bitwarden/common/tools/rx"; import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject"; import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider"; @@ -29,7 +21,7 @@ import { Algorithms, Types, } from "../metadata"; -import { availableAlgorithms_vNext } from "../policies/available-algorithms-policy"; +import { AvailableAlgorithmsConstraint, availableAlgorithms } from "../policies"; import { CredentialPreference } from "../types"; import { AlgorithmRequest, @@ -148,8 +140,15 @@ export class GeneratorMetadataProvider { const policies$ = this.application.policy .policiesByType$(PolicyType.PasswordGenerator, id) .pipe( - map((p) => availableAlgorithms_vNext(p).filter((a) => this._metadata.has(a))), - map((p) => new Set(p)), + map((p) => + availableAlgorithms(p) + .filter((a) => this._metadata.has(a)) + .sort(), + ), + // interning the set transformation lets `distinctUntilChanged()` eliminate + // repeating policy emissions using reference equality + memoizedMap((a) => new Set(a), { key: (a) => a.join(":") }), + distinctUntilChanged(), // complete policy emissions otherwise `switchMap` holds `available$` open indefinitely takeUntil(anyComplete(id$)), ); @@ -211,24 +210,7 @@ export class GeneratorMetadataProvider { const account$ = dependencies.account$.pipe(shareReplay({ bufferSize: 1, refCount: true })); const algorithm$ = this.preferences({ account$ }).pipe( - combineLatestWith(this.isAvailable$({ account$ })), - map(([preferences, isAvailable]) => { - const algorithm: CredentialAlgorithm = preferences[type].algorithm; - if (isAvailable(algorithm)) { - return algorithm; - } - - const algorithms = type ? this.algorithms({ type: type }) : []; - // `?? null` because logging types must be `Jsonify` - const defaultAlgorithm = algorithms.find(isAvailable) ?? null; - this.log.debug( - { algorithm, defaultAlgorithm, credentialType: type }, - "preference not available; defaulting the generator algorithm", - ); - - // `?? undefined` so that interface is ADR-14 compliant - return defaultAlgorithm ?? undefined; - }), + map((preferences) => preferences[type].algorithm), distinctUntilChanged(), ); @@ -246,8 +228,16 @@ export class GeneratorMetadataProvider { preferences( dependencies: BoundDependency<"account", Account>, ): UserStateSubject { - // FIXME: enforce policy - const subject = new UserStateSubject(PREFERENCES, this.system, dependencies); + const account$ = dependencies.account$.pipe(shareReplay({ bufferSize: 1, refCount: true })); + + const constraints$ = this.isAvailable$({ account$ }).pipe( + map( + (isAvailable) => + new AvailableAlgorithmsConstraint(this.algorithms.bind(this), isAvailable, this.system), + ), + ); + + const subject = new UserStateSubject(PREFERENCES, this.system, { account$, constraints$ }); return subject; } diff --git a/libs/tools/generator/core/src/services/generator-profile-provider.spec.ts b/libs/tools/generator/core/src/providers/generator-profile-provider.spec.ts similarity index 99% rename from libs/tools/generator/core/src/services/generator-profile-provider.spec.ts rename to libs/tools/generator/core/src/providers/generator-profile-provider.spec.ts index aeb1a648a14..1053834eca7 100644 --- a/libs/tools/generator/core/src/services/generator-profile-provider.spec.ts +++ b/libs/tools/generator/core/src/providers/generator-profile-provider.spec.ts @@ -67,6 +67,7 @@ const dependencyProvider: UserStateSubjectDependencyProvider = { encryptor: encryptorProvider, state: stateProvider, log: disabledSemanticLoggerProvider, + now: Date.now, }; // settings storage location diff --git a/libs/tools/generator/core/src/services/generator-profile-provider.ts b/libs/tools/generator/core/src/providers/generator-profile-provider.ts similarity index 94% rename from libs/tools/generator/core/src/services/generator-profile-provider.ts rename to libs/tools/generator/core/src/providers/generator-profile-provider.ts index 7088e23d3fe..4117d1f2a78 100644 --- a/libs/tools/generator/core/src/services/generator-profile-provider.ts +++ b/libs/tools/generator/core/src/providers/generator-profile-provider.ts @@ -19,6 +19,7 @@ import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/stat import { ProfileContext, CoreProfileMetadata, ProfileMetadata } from "../metadata"; import { GeneratorConstraints } from "../types/generator-constraints"; +import { equivalent } from "../util"; /** Surfaces contextual information to credential generators */ export class GeneratorProfileProvider { @@ -99,7 +100,10 @@ export class GeneratorProfileProvider { const constraints$ = policies$.pipe( map((policies) => profile.constraints.create(policies, context)), - tap(() => this.log.debug("constraints created")), + distinctUntilChanged((previous, next) => { + return equivalent(previous, next); + }), + tap((constraints) => this.log.debug(constraints as object, "constraints updated")), ); return constraints$; diff --git a/libs/tools/generator/core/src/providers/index.ts b/libs/tools/generator/core/src/providers/index.ts new file mode 100644 index 00000000000..bad56b746b6 --- /dev/null +++ b/libs/tools/generator/core/src/providers/index.ts @@ -0,0 +1,4 @@ +export { CredentialGeneratorProviders } from "./credential-generator-providers"; +export { GeneratorMetadataProvider } from "./generator-metadata-provider"; +export { GeneratorProfileProvider } from "./generator-profile-provider"; +export { GeneratorDependencyProvider } from "./generator-dependency-provider"; diff --git a/libs/tools/generator/core/src/rx.ts b/libs/tools/generator/core/src/rx.ts index 44d23ef1c5c..ab907b6455f 100644 --- a/libs/tools/generator/core/src/rx.ts +++ b/libs/tools/generator/core/src/rx.ts @@ -18,20 +18,6 @@ export function mapPolicyToEvaluator( ); } -/** Maps an administrative console policy to constraints using the provided configuration. - * @param configuration the configuration that constructs the constraints. - */ -export function mapPolicyToConstraints( - configuration: PolicyConfiguration, - email: string, -) { - return pipe( - reduceCollection(configuration.combine, configuration.disabledValue), - distinctIfShallowMatch(), - map((policy) => configuration.toConstraints(policy, email)), - ); -} - /** Constructs a method that maps a policy to the default (no-op) policy. */ export function newDefaultEvaluator() { return () => { diff --git a/libs/tools/generator/core/src/services/credential-generator.service.spec.ts b/libs/tools/generator/core/src/services/credential-generator.service.spec.ts deleted file mode 100644 index 2bc8d514873..00000000000 --- a/libs/tools/generator/core/src/services/credential-generator.service.spec.ts +++ /dev/null @@ -1,1050 +0,0 @@ -// FIXME: remove ts-strict-ignore once `FakeAccountService` implements ts strict support -// @ts-strict-ignore -import { mock } from "jest-mock-extended"; -import { BehaviorSubject, firstValueFrom, map, Subject } from "rxjs"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state"; -import { LegacyEncryptorProvider } from "@bitwarden/common/tools/cryptography/legacy-encryptor-provider"; -import { UserEncryptor } from "@bitwarden/common/tools/cryptography/user-encryptor.abstraction"; -import { disabledSemanticLoggerProvider } from "@bitwarden/common/tools/log"; -import { StateConstraints } from "@bitwarden/common/tools/types"; -import { OrganizationId, PolicyId, UserId } from "@bitwarden/common/types/guid"; - -import { - FakeStateProvider, - FakeAccountService, - awaitAsync, - ObservableTracker, -} from "../../../../../common/spec"; -import { Randomizer } from "../abstractions"; -import { Generators } from "../data"; -import { - CredentialGeneratorConfiguration, - GeneratedCredential, - GenerateRequest, - GeneratorConstraints, -} from "../types"; - -import { CredentialGeneratorService } from "./credential-generator.service"; - -// arbitrary settings types -type SomeSettings = { foo: string }; -type SomePolicy = { fooPolicy: boolean }; - -// settings storage location -const SettingsKey = new UserKeyDefinition(GENERATOR_DISK, "SomeSettings", { - deserializer: (value) => value, - clearOn: [], -}); - -// fake policies -const policyService = mock(); -const somePolicy = new Policy({ - data: { fooPolicy: true }, - type: PolicyType.PasswordGenerator, - id: "" as PolicyId, - organizationId: "" as OrganizationId, - enabled: true, -}); -const passwordOverridePolicy = new Policy({ - id: "" as PolicyId, - organizationId: "", - type: PolicyType.PasswordGenerator, - data: { - overridePasswordType: "password", - }, - enabled: true, -}); - -const passphraseOverridePolicy = new Policy({ - id: "" as PolicyId, - organizationId: "", - type: PolicyType.PasswordGenerator, - data: { - overridePasswordType: "passphrase", - }, - enabled: true, -}); - -const SomeTime = new Date(1); -const SomeAlgorithm = "passphrase"; -const SomeCategory = "password"; -const SomeNameKey = "passphraseKey"; -const SomeGenerateKey = "generateKey"; -const SomeCredentialTypeKey = "credentialTypeKey"; -const SomeOnGeneratedMessageKey = "onGeneratedMessageKey"; -const SomeCopyKey = "copyKey"; -const SomeUseGeneratedValueKey = "useGeneratedValueKey"; - -// fake the configuration -const SomeConfiguration: CredentialGeneratorConfiguration = { - id: SomeAlgorithm, - category: SomeCategory, - nameKey: SomeNameKey, - generateKey: SomeGenerateKey, - onGeneratedMessageKey: SomeOnGeneratedMessageKey, - credentialTypeKey: SomeCredentialTypeKey, - copyKey: SomeCopyKey, - useGeneratedValueKey: SomeUseGeneratedValueKey, - onlyOnRequest: false, - request: [], - engine: { - create: (_randomizer) => { - return { - generate: (request, settings) => { - const result = new GeneratedCredential( - settings.foo, - SomeAlgorithm, - SomeTime, - request.source, - request.website, - ); - return Promise.resolve(result); - }, - }; - }, - }, - settings: { - initial: { foo: "initial" }, - constraints: { foo: {} }, - account: SettingsKey, - }, - policy: { - type: PolicyType.PasswordGenerator, - disabledValue: { - fooPolicy: false, - }, - combine: (acc, policy) => { - return { fooPolicy: acc.fooPolicy || policy.data.fooPolicy }; - }, - createEvaluator: () => { - throw new Error("this should never be called"); - }, - toConstraints: (policy) => { - if (policy.fooPolicy) { - return { - constraints: { - policyInEffect: true, - }, - calibrate(state: SomeSettings) { - return { - constraints: {}, - adjust(state: SomeSettings) { - return { foo: `adjusted(${state.foo})` }; - }, - fix(state: SomeSettings) { - return { foo: `fixed(${state.foo})` }; - }, - } satisfies StateConstraints; - }, - } satisfies GeneratorConstraints; - } else { - return { - constraints: { - policyInEffect: false, - }, - adjust(state: SomeSettings) { - return state; - }, - fix(state: SomeSettings) { - return state; - }, - } satisfies GeneratorConstraints; - } - }, - }, -}; - -// fake user information -const SomeUser = "SomeUser" as UserId; -const AnotherUser = "SomeOtherUser" as UserId; -const accounts = { - [SomeUser]: { - id: SomeUser, - name: "some user", - email: "some.user@example.com", - emailVerified: true, - }, - [AnotherUser]: { - id: AnotherUser, - name: "some other user", - email: "some.other.user@example.com", - emailVerified: true, - }, -}; -const accountService = new FakeAccountService(accounts); - -// fake state -const stateProvider = new FakeStateProvider(accountService); - -// fake randomizer -const randomizer = mock(); - -const i18nService = mock(); - -const apiService = mock(); - -const encryptor = mock(); -const encryptorProvider = mock({ - userEncryptor$(_, dependencies) { - return dependencies.singleUserId$.pipe(map((userId) => ({ userId, encryptor }))); - }, -}); - -const account$ = new BehaviorSubject(accounts[SomeUser]); - -const providers = { - encryptor: encryptorProvider, - state: stateProvider, - log: disabledSemanticLoggerProvider, -}; - -describe("CredentialGeneratorService", () => { - beforeEach(async () => { - await accountService.switchAccount(SomeUser); - policyService.policiesByType$.mockImplementation(() => new BehaviorSubject([]).asObservable()); - i18nService.t.mockImplementation((key: string) => key); - apiService.fetch.mockImplementation(() => Promise.resolve(mock())); - jest.clearAllMocks(); - }); - - describe("generate$", () => { - it("completes when `on$` completes", async () => { - await stateProvider.setUserState(SettingsKey, { foo: "value" }, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const on$ = new Subject(); - let complete = false; - - // confirm no emission during subscription - generator.generate$(SomeConfiguration, { on$, account$ }).subscribe({ - complete: () => { - complete = true; - }, - }); - on$.complete(); - await awaitAsync(); - - expect(complete).toBeTruthy(); - }); - - it("includes request.source in the generated credential", async () => { - const settings = { foo: "value" }; - await stateProvider.setUserState(SettingsKey, settings, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const on$ = new BehaviorSubject({ source: "some source" }); - const generated = new ObservableTracker( - generator.generate$(SomeConfiguration, { on$, account$ }), - ); - - const result = await generated.expectEmission(); - - expect(result.source).toEqual("some source"); - }); - - it("includes request.website in the generated credential", async () => { - const settings = { foo: "value" }; - await stateProvider.setUserState(SettingsKey, settings, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const on$ = new BehaviorSubject({ website: "some website" }); - const generated = new ObservableTracker( - generator.generate$(SomeConfiguration, { on$, account$ }), - ); - - const result = await generated.expectEmission(); - - expect(result.website).toEqual("some website"); - }); - - // FIXME: test these when the fake state provider can create the required emissions - it.todo("errors when the settings error"); - it.todo("completes when the settings complete"); - - it("emits a generation for a specific user when `user$` supplied", async () => { - await stateProvider.setUserState(SettingsKey, { foo: "value" }, SomeUser); - await stateProvider.setUserState(SettingsKey, { foo: "another" }, AnotherUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account$ = new BehaviorSubject(accounts[AnotherUser]).asObservable(); - const on$ = new Subject(); - const generated = new ObservableTracker( - generator.generate$(SomeConfiguration, { on$, account$ }), - ); - on$.next({}); - - const result = await generated.expectEmission(); - - expect(result).toEqual(new GeneratedCredential("another", SomeAlgorithm, SomeTime)); - }); - - it("errors when `user$` errors", async () => { - await stateProvider.setUserState(SettingsKey, null, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const on$ = new Subject(); - const account$ = new BehaviorSubject(accounts[SomeUser]); - let error = null; - - generator.generate$(SomeConfiguration, { on$, account$ }).subscribe({ - error: (e: unknown) => { - error = e; - }, - }); - account$.error({ some: "error" }); - await awaitAsync(); - - expect(error).toEqual({ some: "error" }); - }); - - it("completes when `user$` completes", async () => { - await stateProvider.setUserState(SettingsKey, null, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const on$ = new Subject(); - const account$ = new BehaviorSubject(accounts[SomeUser]); - let completed = false; - - generator.generate$(SomeConfiguration, { on$, account$ }).subscribe({ - complete: () => { - completed = true; - }, - }); - account$.complete(); - await awaitAsync(); - - expect(completed).toBeTruthy(); - }); - - it("emits a generation only when `on$` emits", async () => { - // This test breaks from arrange/act/assert because it is testing causality - await stateProvider.setUserState(SettingsKey, { foo: "value" }, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const on$ = new Subject(); - const results: any[] = []; - - // confirm no emission during subscription - const sub = generator - .generate$(SomeConfiguration, { on$, account$ }) - .subscribe((result) => results.push(result)); - await awaitAsync(); - expect(results.length).toEqual(0); - - // confirm forwarded emission - on$.next({}); - await awaitAsync(); - expect(results).toEqual([new GeneratedCredential("value", SomeAlgorithm, SomeTime)]); - - // confirm updating settings does not cause an emission - await stateProvider.setUserState(SettingsKey, { foo: "next" }, SomeUser); - await awaitAsync(); - expect(results.length).toBe(1); - - // confirm forwarded emission takes latest value - on$.next({}); - await awaitAsync(); - sub.unsubscribe(); - - expect(results).toEqual([ - new GeneratedCredential("value", SomeAlgorithm, SomeTime), - new GeneratedCredential("next", SomeAlgorithm, SomeTime), - ]); - }); - - it("errors when `on$` errors", async () => { - await stateProvider.setUserState(SettingsKey, { foo: "value" }, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const on$ = new Subject(); - let error: any = null; - - // confirm no emission during subscription - generator.generate$(SomeConfiguration, { on$, account$ }).subscribe({ - error: (e: unknown) => { - error = e; - }, - }); - on$.error({ some: "error" }); - await awaitAsync(); - - expect(error).toEqual({ some: "error" }); - }); - - // FIXME: test these when the fake state provider can delay its first emission - it.todo("emits when settings$ become available if on$ is called before they're ready."); - }); - - describe("algorithms", () => { - it("outputs password generation metadata", () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = generator.algorithms("password"); - - expect(result.some((a) => a.id === Generators.password.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.passphrase.id)).toBeTruthy(); - - // this test shouldn't contain entries outside of the current category - expect(result.some((a) => a.id === Generators.username.id)).toBeFalsy(); - expect(result.some((a) => a.id === Generators.catchall.id)).toBeFalsy(); - }); - - it("outputs username generation metadata", () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = generator.algorithms("username"); - - expect(result.some((a) => a.id === Generators.username.id)).toBeTruthy(); - - // this test shouldn't contain entries outside of the current category - expect(result.some((a) => a.id === Generators.catchall.id)).toBeFalsy(); - expect(result.some((a) => a.id === Generators.password.id)).toBeFalsy(); - }); - - it("outputs email generation metadata", () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = generator.algorithms("email"); - - expect(result.some((a) => a.id === Generators.catchall.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.subaddress.id)).toBeTruthy(); - - // this test shouldn't contain entries outside of the current category - expect(result.some((a) => a.id === Generators.username.id)).toBeFalsy(); - expect(result.some((a) => a.id === Generators.password.id)).toBeFalsy(); - }); - - it("combines metadata across categories", () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = generator.algorithms(["username", "email"]); - - expect(result.some((a) => a.id === Generators.username.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.catchall.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.subaddress.id)).toBeTruthy(); - - // this test shouldn't contain entries outside of the current categories - expect(result.some((a) => a.id === Generators.password.id)).toBeFalsy(); - }); - }); - - describe("algorithms$", () => { - // these tests cannot use the observable tracker because they return - // data that cannot be cloned - it("returns password metadata", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom(generator.algorithms$("password", { account$ })); - - expect(result.some((a) => a.id === Generators.password.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.passphrase.id)).toBeTruthy(); - }); - - it("returns username metadata", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom(generator.algorithms$("username", { account$ })); - - expect(result.some((a) => a.id === Generators.username.id)).toBeTruthy(); - }); - - it("returns email metadata", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom(generator.algorithms$("email", { account$ })); - - expect(result.some((a) => a.id === Generators.catchall.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.subaddress.id)).toBeTruthy(); - }); - - it("returns username and email metadata", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom( - generator.algorithms$(["username", "email"], { account$ }), - ); - - expect(result.some((a) => a.id === Generators.username.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.catchall.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.subaddress.id)).toBeTruthy(); - }); - - // Subsequent tests focus on passwords and passphrases as an example of policy - // awareness; they exercise the logic without being comprehensive - it("enforces the active user's policy", async () => { - const policy$ = new BehaviorSubject([passwordOverridePolicy]); - policyService.policiesByType$.mockReturnValue(policy$); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom(generator.algorithms$(["password"], { account$ })); - - expect(policyService.policiesByType$).toHaveBeenCalledWith( - PolicyType.PasswordGenerator, - SomeUser, - ); - expect(result.some((a) => a.id === Generators.password.id)).toBeTruthy(); - expect(result.some((a) => a.id === Generators.passphrase.id)).toBeFalsy(); - }); - - it("follows changes to the active user", async () => { - const account$ = new BehaviorSubject(accounts[SomeUser]); - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passwordOverridePolicy]), - ); - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passphraseOverridePolicy]), - ); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const results: any = []; - const sub = generator.algorithms$("password", { account$ }).subscribe((r) => results.push(r)); - - account$.next(accounts[AnotherUser]); - await awaitAsync(); - sub.unsubscribe(); - - const [someResult, anotherResult] = results; - - expect(policyService.policiesByType$).toHaveBeenNthCalledWith( - 1, - PolicyType.PasswordGenerator, - SomeUser, - ); - expect(someResult.some((a: any) => a.id === Generators.password.id)).toBeTruthy(); - expect(someResult.some((a: any) => a.id === Generators.passphrase.id)).toBeFalsy(); - - expect(policyService.policiesByType$).toHaveBeenNthCalledWith( - 2, - PolicyType.PasswordGenerator, - AnotherUser, - ); - expect(anotherResult.some((a: any) => a.id === Generators.passphrase.id)).toBeTruthy(); - expect(anotherResult.some((a: any) => a.id === Generators.password.id)).toBeFalsy(); - }); - - it("reads an arbitrary user's settings", async () => { - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passwordOverridePolicy]), - ); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account$ = new BehaviorSubject(accounts[AnotherUser]).asObservable(); - - const result = await firstValueFrom(generator.algorithms$("password", { account$ })); - - expect(policyService.policiesByType$).toHaveBeenCalledWith( - PolicyType.PasswordGenerator, - AnotherUser, - ); - expect(result.some((a: any) => a.id === Generators.password.id)).toBeTruthy(); - expect(result.some((a: any) => a.id === Generators.passphrase.id)).toBeFalsy(); - }); - - it("follows changes to the arbitrary user", async () => { - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passwordOverridePolicy]), - ); - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passphraseOverridePolicy]), - ); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - const results: any = []; - const sub = generator.algorithms$("password", { account$ }).subscribe((r) => results.push(r)); - - account.next(accounts[AnotherUser]); - await awaitAsync(); - sub.unsubscribe(); - - const [someResult, anotherResult] = results; - expect(policyService.policiesByType$).toHaveBeenCalledWith( - PolicyType.PasswordGenerator, - SomeUser, - ); - expect(someResult.some((a: any) => a.id === Generators.password.id)).toBeTruthy(); - expect(someResult.some((a: any) => a.id === Generators.passphrase.id)).toBeFalsy(); - - expect(policyService.policiesByType$).toHaveBeenCalledWith( - PolicyType.PasswordGenerator, - AnotherUser, - ); - expect(anotherResult.some((a: any) => a.id === Generators.passphrase.id)).toBeTruthy(); - expect(anotherResult.some((a: any) => a.id === Generators.password.id)).toBeFalsy(); - }); - - it("errors when the arbitrary user's stream errors", async () => { - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passwordOverridePolicy]), - ); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - let error = null; - - generator.algorithms$("password", { account$ }).subscribe({ - error: (e: unknown) => { - error = e; - }, - }); - account.error({ some: "error" }); - await awaitAsync(); - - expect(error).toEqual({ some: "error" }); - }); - - it("completes when the arbitrary user's stream completes", async () => { - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passwordOverridePolicy]), - ); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - let completed = false; - - generator.algorithms$("password", { account$ }).subscribe({ - complete: () => { - completed = true; - }, - }); - account.complete(); - await awaitAsync(); - - expect(completed).toBeTruthy(); - }); - - it("ignores repeated arbitrary user emissions", async () => { - policyService.policiesByType$.mockReturnValueOnce( - new BehaviorSubject([passwordOverridePolicy]), - ); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - let count = 0; - - const sub = generator.algorithms$("password", { account$ }).subscribe({ - next: () => { - count++; - }, - }); - await awaitAsync(); - account.next(accounts[SomeUser]); - await awaitAsync(); - account.next(accounts[SomeUser]); - await awaitAsync(); - sub.unsubscribe(); - - expect(count).toEqual(1); - }); - }); - - describe("settings$", () => { - it("defaults to the configuration's initial settings if settings aren't found", async () => { - await stateProvider.setUserState(SettingsKey, null, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom(generator.settings$(SomeConfiguration, { account$ })); - - expect(result).toEqual(SomeConfiguration.settings.initial); - }); - - it("reads from the active user's configuration-defined storage", async () => { - const settings = { foo: "value" }; - await stateProvider.setUserState(SettingsKey, settings, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom(generator.settings$(SomeConfiguration, { account$ })); - - expect(result).toEqual(settings); - }); - - it("applies policy to the loaded settings", async () => { - const settings = { foo: "value" }; - await stateProvider.setUserState(SettingsKey, settings, SomeUser); - const policy$ = new BehaviorSubject([somePolicy]); - policyService.policiesByType$.mockReturnValue(policy$); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - - const result = await firstValueFrom(generator.settings$(SomeConfiguration, { account$ })); - - expect(result).toEqual({ foo: "adjusted(value)" }); - }); - - it("reads an arbitrary user's settings", async () => { - await stateProvider.setUserState(SettingsKey, { foo: "value" }, SomeUser); - const anotherSettings = { foo: "another" }; - await stateProvider.setUserState(SettingsKey, anotherSettings, AnotherUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account$ = new BehaviorSubject(accounts[AnotherUser]).asObservable(); - - const result = await firstValueFrom(generator.settings$(SomeConfiguration, { account$ })); - - expect(result).toEqual(anotherSettings); - }); - - it("errors when the arbitrary user's stream errors", async () => { - await stateProvider.setUserState(SettingsKey, null, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - let error = null; - - generator.settings$(SomeConfiguration, { account$ }).subscribe({ - error: (e: unknown) => { - error = e; - }, - }); - account.error({ some: "error" }); - await awaitAsync(); - - expect(error).toEqual({ some: "error" }); - }); - - it("completes when the arbitrary user's stream completes", async () => { - await stateProvider.setUserState(SettingsKey, null, SomeUser); - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - let completed = false; - - generator.settings$(SomeConfiguration, { account$ }).subscribe({ - complete: () => { - completed = true; - }, - }); - account.complete(); - await awaitAsync(); - - expect(completed).toBeTruthy(); - }); - }); - - describe("settings", () => { - it("writes to the user's state", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const subject = generator.settings(SomeConfiguration, { account$ }); - - subject.next({ foo: "next value" }); - await awaitAsync(); - const result = await firstValueFrom(stateProvider.getUserState$(SettingsKey, SomeUser)); - - expect(result).toEqual({ - foo: "next value", - }); - }); - }); - - describe("policy$", () => { - it("creates constraints without policy in effect when there is no policy", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account$ = new BehaviorSubject(accounts[SomeUser]).asObservable(); - - const result = await firstValueFrom(generator.policy$(SomeConfiguration, { account$ })); - - expect(result.constraints.policyInEffect).toBeFalsy(); - }); - - it("creates constraints with policy in effect when there is a policy", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account$ = new BehaviorSubject(accounts[SomeUser]).asObservable(); - const policy$ = new BehaviorSubject([somePolicy]); - policyService.policiesByType$.mockReturnValue(policy$); - - const result = await firstValueFrom(generator.policy$(SomeConfiguration, { account$ })); - - expect(result.constraints.policyInEffect).toBeTruthy(); - }); - - it("follows policy emissions", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - const somePolicySubject = new BehaviorSubject([somePolicy]); - policyService.policiesByType$.mockReturnValueOnce(somePolicySubject.asObservable()); - const emissions: GeneratorConstraints[] = []; - const sub = generator - .policy$(SomeConfiguration, { account$ }) - .subscribe((policy) => emissions.push(policy)); - - // swap the active policy for an inactive policy - somePolicySubject.next([]); - await awaitAsync(); - sub.unsubscribe(); - const [someResult, anotherResult] = emissions; - - expect(someResult.constraints.policyInEffect).toBeTruthy(); - expect(anotherResult.constraints.policyInEffect).toBeFalsy(); - }); - - it("follows user emissions", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - const somePolicy$ = new BehaviorSubject([somePolicy]).asObservable(); - const anotherPolicy$ = new BehaviorSubject([]).asObservable(); - policyService.policiesByType$ - .mockReturnValueOnce(somePolicy$) - .mockReturnValueOnce(anotherPolicy$); - const emissions: GeneratorConstraints[] = []; - const sub = generator - .policy$(SomeConfiguration, { account$ }) - .subscribe((policy) => emissions.push(policy)); - - // swapping the user invokes the return for `anotherPolicy$` - account.next(accounts[AnotherUser]); - await awaitAsync(); - sub.unsubscribe(); - const [someResult, anotherResult] = emissions; - - expect(someResult.constraints.policyInEffect).toBeTruthy(); - expect(anotherResult.constraints.policyInEffect).toBeFalsy(); - }); - - it("errors when the user errors", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - const expectedError = { some: "error" }; - - let actualError: any = null; - generator.policy$(SomeConfiguration, { account$ }).subscribe({ - error: (e: unknown) => { - actualError = e; - }, - }); - account.error(expectedError); - await awaitAsync(); - - expect(actualError).toEqual(expectedError); - }); - - it("completes when the user completes", async () => { - const generator = new CredentialGeneratorService( - randomizer, - policyService, - apiService, - i18nService, - providers, - ); - const account = new BehaviorSubject(accounts[SomeUser]); - const account$ = account.asObservable(); - - let completed = false; - generator.policy$(SomeConfiguration, { account$ }).subscribe({ - complete: () => { - completed = true; - }, - }); - account.complete(); - await awaitAsync(); - - expect(completed).toBeTruthy(); - }); - }); -}); diff --git a/libs/tools/generator/core/src/services/credential-generator.service.ts b/libs/tools/generator/core/src/services/credential-generator.service.ts deleted file mode 100644 index eacc2ca6fc5..00000000000 --- a/libs/tools/generator/core/src/services/credential-generator.service.ts +++ /dev/null @@ -1,296 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { concatMap, distinctUntilChanged, map, Observable, switchMap, takeUntil } from "rxjs"; -import { Simplify } from "type-fest"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { Account } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { BoundDependency, OnDependency } from "@bitwarden/common/tools/dependencies"; -import { IntegrationMetadata } from "@bitwarden/common/tools/integration"; -import { RestClient } from "@bitwarden/common/tools/integration/rpc"; -import { anyComplete, withLatestReady } from "@bitwarden/common/tools/rx"; -import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject"; -import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider"; - -import { Randomizer } from "../abstractions"; -import { - Generators, - getForwarderConfiguration, - Integrations, - toCredentialGeneratorConfiguration, -} from "../data"; -import { availableAlgorithms } from "../policies/available-algorithms-policy"; -import { mapPolicyToConstraints } from "../rx"; -import { - CredentialAlgorithm, - CredentialCategories, - CredentialCategory, - AlgorithmInfo, - CredentialPreference, - isForwarderIntegration, - ForwarderIntegration, - GenerateRequest, -} from "../types"; -import { - CredentialGeneratorConfiguration as Configuration, - CredentialGeneratorInfo, - GeneratorDependencyProvider, -} from "../types/credential-generator-configuration"; -import { GeneratorConstraints } from "../types/generator-constraints"; - -import { PREFERENCES } from "./credential-preferences"; - -type Generate$Dependencies = Simplify< - OnDependency & BoundDependency<"account", Account> ->; - -export class CredentialGeneratorService { - constructor( - private readonly randomizer: Randomizer, - private readonly policyService: PolicyService, - private readonly apiService: ApiService, - private readonly i18nService: I18nService, - private readonly providers: UserStateSubjectDependencyProvider, - ) {} - - private getDependencyProvider(): GeneratorDependencyProvider { - return { - client: new RestClient(this.apiService, this.i18nService), - i18nService: this.i18nService, - randomizer: this.randomizer, - }; - } - - // FIXME: the rxjs methods of this service can be a lot more resilient if - // `Subjects` are introduced where sharing occurs - - /** Generates a stream of credentials - * @param configuration determines which generator's settings are loaded - * @param dependencies.on$ Required. A new credential is emitted when this emits. - */ - generate$( - configuration: Readonly>, - dependencies: Generate$Dependencies, - ) { - const engine = configuration.engine.create(this.getDependencyProvider()); - const settings$ = this.settings$(configuration, dependencies); - - // generation proper - const generate$ = dependencies.on$.pipe( - withLatestReady(settings$), - concatMap(([request, settings]) => engine.generate(request, settings)), - takeUntil(anyComplete([settings$])), - ); - - return generate$; - } - - /** Emits metadata concerning the provided generation algorithms - * @param category the category or categories of interest - * @param dependences.account$ algorithms are filtered to only - * those matching the provided account's policy. - * @returns An observable that emits algorithm metadata. - */ - algorithms$( - category: CredentialCategory, - dependencies: BoundDependency<"account", Account>, - ): Observable; - algorithms$( - category: CredentialCategory[], - dependencies: BoundDependency<"account", Account>, - ): Observable; - algorithms$( - category: CredentialCategory | CredentialCategory[], - dependencies: BoundDependency<"account", Account>, - ) { - // any cast required here because TypeScript fails to bind `category` - // to the union-typed overload of `algorithms`. - const algorithms = this.algorithms(category as any); - - // apply policy - const algorithms$ = dependencies.account$.pipe( - distinctUntilChanged(), - switchMap((account) => { - const policies$ = this.policyService - .policiesByType$(PolicyType.PasswordGenerator, account.id) - .pipe( - map((p) => new Set(availableAlgorithms(p))), - // complete policy emissions otherwise `switchMap` holds `algorithms$` open indefinitely - takeUntil(anyComplete(dependencies.account$)), - ); - return policies$; - }), - map((available) => { - const filtered = algorithms.filter( - (c) => isForwarderIntegration(c.id) || available.has(c.id), - ); - return filtered; - }), - ); - - return algorithms$; - } - - /** Lists metadata for the algorithms in a credential category - * @param category the category or categories of interest - * @returns A list containing the requested metadata. - */ - algorithms(category: CredentialCategory): AlgorithmInfo[]; - algorithms(category: CredentialCategory[]): AlgorithmInfo[]; - algorithms(category: CredentialCategory | CredentialCategory[]): AlgorithmInfo[] { - const categories: CredentialCategory[] = Array.isArray(category) ? category : [category]; - - const algorithms = categories - .flatMap((c) => CredentialCategories[c] as CredentialAlgorithm[]) - .map((id) => this.algorithm(id)) - .filter((info) => info !== null); - - const forwarders = Object.keys(Integrations) - .map((key: keyof typeof Integrations) => { - const forwarder: ForwarderIntegration = { forwarder: Integrations[key].id }; - return this.algorithm(forwarder); - }) - .filter((forwarder) => categories.includes(forwarder.category)); - - return algorithms.concat(forwarders); - } - - /** Look up the metadata for a specific generator algorithm - * @param id identifies the algorithm - * @returns the requested metadata, or `null` if the metadata wasn't found. - */ - algorithm(id: CredentialAlgorithm): AlgorithmInfo { - let generator: CredentialGeneratorInfo = null; - let integration: IntegrationMetadata = null; - - if (isForwarderIntegration(id)) { - const forwarderConfig = getForwarderConfiguration(id.forwarder); - integration = forwarderConfig; - - if (forwarderConfig) { - generator = toCredentialGeneratorConfiguration(forwarderConfig); - } - } else { - generator = Generators[id]; - } - - if (!generator) { - throw new Error(`Invalid credential algorithm: ${JSON.stringify(id)}`); - } - - const info: AlgorithmInfo = { - id: generator.id, - category: generator.category, - name: integration ? integration.name : this.i18nService.t(generator.nameKey), - generate: this.i18nService.t(generator.generateKey), - onGeneratedMessage: this.i18nService.t(generator.onGeneratedMessageKey), - credentialType: this.i18nService.t(generator.credentialTypeKey), - copy: this.i18nService.t(generator.copyKey), - useGeneratedValue: this.i18nService.t(generator.useGeneratedValueKey), - onlyOnRequest: generator.onlyOnRequest, - request: generator.request, - }; - - if (generator.descriptionKey) { - info.description = this.i18nService.t(generator.descriptionKey); - } - - return info; - } - - /** Get the settings for the provided configuration - * @param configuration determines which generator's settings are loaded - * @param dependencies.account$ identifies the account to which the settings are bound. - * @returns an observable that emits settings - * @remarks the observable enforces policies on the settings - */ - settings$( - configuration: Configuration, - dependencies: BoundDependency<"account", Account>, - ) { - const constraints$ = this.policy$(configuration, dependencies); - - const settings = new UserStateSubject(configuration.settings.account, this.providers, { - constraints$, - account$: dependencies.account$, - }); - - const settings$ = settings.pipe( - map((settings) => settings ?? structuredClone(configuration.settings.initial)), - ); - - return settings$; - } - - /** Get a subject bound to credential generator preferences. - * @param dependencies.account$ identifies the account to which the preferences are bound - * @returns a subject bound to the user's preferences - * @remarks Preferences determine which algorithms are used when generating a - * credential from a credential category (e.g. `PassX` or `Username`). Preferences - * should not be used to hold navigation history. Use @bitwarden/generator-navigation - * instead. - */ - preferences( - dependencies: BoundDependency<"account", Account>, - ): UserStateSubject { - // FIXME: enforce policy - const subject = new UserStateSubject(PREFERENCES, this.providers, dependencies); - - return subject; - } - - /** Get a subject bound to a specific user's settings - * @param configuration determines which generator's settings are loaded - * @param dependencies.account$ identifies the account to which the settings are bound - * @returns a subject bound to the requested user's generator settings - * @remarks the subject enforces policy for the settings - */ - settings( - configuration: Readonly>, - dependencies: BoundDependency<"account", Account>, - ) { - const constraints$ = this.policy$(configuration, dependencies); - - const subject = new UserStateSubject(configuration.settings.account, this.providers, { - constraints$, - account$: dependencies.account$, - }); - - return subject; - } - - /** Get the policy constraints for the provided configuration - * @param dependencies.account$ determines which user's policy is loaded - * @returns an observable that emits the policy once `dependencies.account$` - * and the policy become available. - */ - policy$( - configuration: Configuration, - dependencies: BoundDependency<"account", Account>, - ): Observable> { - const constraints$ = dependencies.account$.pipe( - map((account) => { - if (account.emailVerified) { - return { userId: account.id, email: account.email }; - } - - return { userId: account.id, email: null }; - }), - switchMap(({ userId, email }) => { - // complete policy emissions otherwise `switchMap` holds `policies$` open indefinitely - const policies$ = this.policyService - .policiesByType$(configuration.policy.type, userId) - .pipe( - mapPolicyToConstraints(configuration.policy, email), - takeUntil(anyComplete(dependencies.account$)), - ); - return policies$; - }), - ); - - return constraints$; - } -} diff --git a/libs/tools/generator/core/src/services/credential-preferences.spec.ts b/libs/tools/generator/core/src/services/credential-preferences.spec.ts deleted file mode 100644 index fc7c3e1bbc6..00000000000 --- a/libs/tools/generator/core/src/services/credential-preferences.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { DefaultCredentialPreferences } from "../data"; - -import { PREFERENCES } from "./credential-preferences"; - -describe("PREFERENCES", () => { - describe("deserializer", () => { - it.each([[null], [undefined]])("creates new preferences (= %p)", (value) => { - const result = PREFERENCES.deserializer(value); - - expect(result).toEqual(DefaultCredentialPreferences); - }); - - it("fills missing password preferences", () => { - const input = { ...DefaultCredentialPreferences }; - delete input.password; - - const result = PREFERENCES.deserializer(input as any); - - expect(result).toEqual(DefaultCredentialPreferences); - }); - - it("fills missing email preferences", () => { - const input = { ...DefaultCredentialPreferences }; - delete input.email; - - const result = PREFERENCES.deserializer(input as any); - - expect(result).toEqual(DefaultCredentialPreferences); - }); - - it("fills missing username preferences", () => { - const input = { ...DefaultCredentialPreferences }; - delete input.username; - - const result = PREFERENCES.deserializer(input as any); - - expect(result).toEqual(DefaultCredentialPreferences); - }); - - it("converts updated fields to Dates", () => { - const input = structuredClone(DefaultCredentialPreferences); - input.email.updated = "1970-01-01T00:00:00.100Z" as any; - input.password.updated = "1970-01-01T00:00:00.200Z" as any; - input.username.updated = "1970-01-01T00:00:00.300Z" as any; - - const result = PREFERENCES.deserializer(input as any); - - expect(result.email.updated).toEqual(new Date(100)); - expect(result.password.updated).toEqual(new Date(200)); - expect(result.username.updated).toEqual(new Date(300)); - }); - }); -}); diff --git a/libs/tools/generator/core/src/services/credential-preferences.ts b/libs/tools/generator/core/src/services/credential-preferences.ts deleted file mode 100644 index 3f6a6c1e1bd..00000000000 --- a/libs/tools/generator/core/src/services/credential-preferences.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state"; - -import { DefaultCredentialPreferences } from "../data"; -import { CredentialPreference } from "../types"; - -/** plaintext password generation options */ -export const PREFERENCES = new UserKeyDefinition( - GENERATOR_DISK, - "credentialPreferences", - { - deserializer: (value) => { - const result = (value as any) ?? {}; - - for (const key in DefaultCredentialPreferences) { - // bind `key` to `category` to transmute the type - const category: keyof typeof DefaultCredentialPreferences = key as any; - - const preference = result[category] ?? { ...DefaultCredentialPreferences[category] }; - if (typeof preference.updated === "string") { - preference.updated = new Date(preference.updated); - } - - result[category] = preference; - } - - return result; - }, - clearOn: ["logout"], - }, -); diff --git a/libs/tools/generator/core/src/services/default-credential-generator.service.spec.ts b/libs/tools/generator/core/src/services/default-credential-generator.service.spec.ts new file mode 100644 index 00000000000..81e7ae6ac63 --- /dev/null +++ b/libs/tools/generator/core/src/services/default-credential-generator.service.spec.ts @@ -0,0 +1,356 @@ +import { BehaviorSubject, Subject, firstValueFrom, of } from "rxjs"; + +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; +import { Site, VendorId } from "@bitwarden/common/tools/extension"; +import { Bitwarden } from "@bitwarden/common/tools/extension/vendor/bitwarden"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; +import { SemanticLogger, ifEnabledSemanticLoggerProvider } from "@bitwarden/common/tools/log"; +import { UserId } from "@bitwarden/common/types/guid"; + +import { awaitAsync } from "../../../../../common/spec"; +import { + Algorithm, + CredentialAlgorithm, + CredentialType, + ForwarderExtensionId, + GeneratorMetadata, + Profile, + Type, +} from "../metadata"; +import { CredentialGeneratorProviders } from "../providers"; +import { GenerateRequest, GeneratedCredential } from "../types"; + +import { DefaultCredentialGeneratorService } from "./default-credential-generator.service"; + +// Custom type for jest.fn() mocks to preserve their type +type JestMockFunction any> = jest.Mock, Parameters>; + +// two-level partial that preserves jest.fn() mock types +type MockTwoLevelPartial = { + [K in keyof T]?: T[K] extends object + ? { + [P in keyof T[K]]?: T[K][P] extends (...args: any) => any + ? JestMockFunction + : T[K][P]; + } + : T[K]; +}; + +describe("DefaultCredentialGeneratorService", () => { + let service: DefaultCredentialGeneratorService; + let providers: MockTwoLevelPartial; + let system: any; + let log: SemanticLogger; + let mockExtension: { settings: jest.Mock }; + let account: Account; + let createService: (overrides?: any) => DefaultCredentialGeneratorService; + + beforeEach(() => { + log = ifEnabledSemanticLoggerProvider(false, new ConsoleLogService(true), { + from: "DefaultCredentialGeneratorService tests", + }); + + mockExtension = { settings: jest.fn() }; + + // Use a hard-coded value for mockAccount + account = { + id: "test-account-id" as UserId, + emailVerified: true, + email: "test@example.com", + name: "Test User", + }; + + system = { + log: jest.fn().mockReturnValue(log), + extension: mockExtension, + }; + + providers = { + metadata: { + metadata: jest.fn(), + preference$: jest.fn(), + algorithms$: jest.fn(), + algorithms: jest.fn(), + preferences: jest.fn(), + }, + profile: { + settings: jest.fn(), + constraints$: jest.fn(), + }, + generator: {}, + }; + + // Creating the service instance with a cast to the expected type + createService = (overrides = {}) => { + // Force cast the incomplete providers to the required type + // similar to how the overrides are applied + const providersCast = providers as unknown as CredentialGeneratorProviders; + + const instance = new DefaultCredentialGeneratorService(providersCast, system); + Object.assign(instance, overrides); + return instance; + }; + + service = createService(); + }); + + describe("generate$", () => { + it("should generate credentials when provided a specific algorithm", async () => { + const mockEngine = { + generate: jest + .fn() + .mockReturnValue( + of( + new GeneratedCredential("generatedPassword", Type.password, Date.now(), "unit test"), + ), + ), + }; + const mockMetadata = { + id: Algorithm.password, + engine: { create: jest.fn().mockReturnValue(mockEngine) }, + } as unknown as GeneratorMetadata; + const mockSettings = new BehaviorSubject({ length: 12 }); + providers.metadata!.metadata = jest.fn().mockReturnValue(mockMetadata); + service = createService({ + settings: () => mockSettings as any, + }); + const on$ = new Subject(); + const account$ = new BehaviorSubject(account); + const result$ = new BehaviorSubject(null); + + service.generate$({ on$, account$ }).subscribe(result$); + on$.next({ algorithm: Algorithm.password }); + await awaitAsync(); + + expect(result$.value?.credential).toEqual("generatedPassword"); + expect(providers.metadata!.metadata).toHaveBeenCalledWith(Algorithm.password); + expect(mockMetadata.engine.create).toHaveBeenCalled(); + expect(mockEngine.generate).toHaveBeenCalled(); + }); + + it("should determine preferred algorithm from credential type and generate credentials", async () => { + const mockEngine = { + generate: jest + .fn() + .mockReturnValue( + of(new GeneratedCredential("generatedPassword", "password", Date.now(), "unit test")), + ), + }; + const mockMetadata = { + id: "testAlgorithm", + engine: { create: jest.fn().mockReturnValue(mockEngine) }, + } as unknown as GeneratorMetadata; + const mockSettings = new BehaviorSubject({ length: 12 }); + + providers.metadata!.preference$ = jest + .fn() + .mockReturnValue(of("testAlgorithm" as CredentialAlgorithm)); + providers.metadata!.metadata = jest.fn().mockReturnValue(mockMetadata); + service = createService({ + settings: () => mockSettings as any, + }); + + const on$ = new Subject(); + const account$ = new BehaviorSubject(account); + const result$ = new BehaviorSubject(null); + + service.generate$({ on$, account$ }).subscribe(result$); + on$.next({ type: Type.password }); + await awaitAsync(); + + expect(result$.value?.credential).toBe("generatedPassword"); + expect(result$.value?.category).toBe(Type.password); + expect(providers.metadata!.metadata).toHaveBeenCalledWith("testAlgorithm"); + }); + }); + + describe("algorithms$", () => { + it("should retrieve and map available algorithms for a credential type", async () => { + const mockAlgorithms = [Algorithm.password, Algorithm.passphrase] as CredentialAlgorithm[]; + const mockMetadata1 = { id: Algorithm.password } as GeneratorMetadata; + const mockMetadata2 = { id: Algorithm.passphrase } as GeneratorMetadata; + + providers.metadata!.algorithms$ = jest.fn().mockReturnValue(of(mockAlgorithms)); + providers.metadata!.metadata = jest + .fn() + .mockReturnValueOnce(mockMetadata1) + .mockReturnValueOnce(mockMetadata2); + + const result = await firstValueFrom( + service.algorithms$("password" as CredentialType, { account$: of(account) }), + ); + + expect(result).toEqual([mockMetadata1, mockMetadata2]); + }); + }); + + describe("algorithms", () => { + it("should list algorithm metadata for a single credential type", () => { + providers.metadata!.algorithms = jest + .fn() + .mockReturnValue([Algorithm.password, Algorithm.passphrase] as CredentialAlgorithm[]); + service = createService({ + algorithm: (id: CredentialAlgorithm) => ({ id }) as GeneratorMetadata, + }); + + const result = service.algorithms("password" as CredentialType); + + expect(result).toEqual([{ id: Algorithm.password }, { id: Algorithm.passphrase }]); + expect(providers.metadata!.algorithms).toHaveBeenCalledWith({ type: "password" }); + }); + + it("should list combined algorithm metadata for multiple credential types", () => { + providers.metadata!.algorithms = jest + .fn() + .mockReturnValueOnce([Algorithm.password] as CredentialAlgorithm[]) + .mockReturnValueOnce([Algorithm.username] as CredentialAlgorithm[]); + + service = createService({ + algorithm: (id: CredentialAlgorithm) => ({ id }) as GeneratorMetadata, + }); + + const result = service.algorithms(["password", "username"] as CredentialType[]); + + expect(result).toEqual([{ id: Algorithm.password }, { id: Algorithm.username }]); + expect(providers.metadata!.algorithms).toHaveBeenCalledWith({ type: "password" }); + expect(providers.metadata!.algorithms).toHaveBeenCalledWith({ type: "username" }); + }); + }); + + describe("algorithm", () => { + it("should retrieve metadata for a specific generator algorithm", () => { + const mockMetadata = { id: Algorithm.password } as GeneratorMetadata; + providers.metadata!.metadata = jest.fn().mockReturnValue(mockMetadata); + + const result = service.algorithm(Algorithm.password); + + expect(result).toBe(mockMetadata); + expect(providers.metadata!.metadata).toHaveBeenCalledWith(Algorithm.password); + }); + + it("should log a panic when algorithm ID is invalid", () => { + providers.metadata!.metadata = jest.fn().mockReturnValue(null); + + expect(() => service.algorithm("invalidAlgo" as CredentialAlgorithm)).toThrow( + "invalid credential algorithm", + ); + }); + }); + + describe("forwarder", () => { + it("should retrieve forwarder metadata for a specific vendor", () => { + const vendorId = Vendor.bitwarden; + const forwarderExtensionId: ForwarderExtensionId = { forwarder: vendorId }; + const mockMetadata = { + id: forwarderExtensionId, + type: "email" as CredentialType, + } as GeneratorMetadata; + + providers.metadata!.metadata = jest.fn().mockReturnValue(mockMetadata); + + const result = service.forwarder(vendorId); + + expect(result).toBe(mockMetadata); + expect(providers.metadata!.metadata).toHaveBeenCalledWith(forwarderExtensionId); + }); + + it("should log a panic when vendor ID is invalid", () => { + const invalidVendorId = "invalid-vendor" as VendorId; + providers.metadata!.metadata = jest.fn().mockReturnValue(null); + + expect(() => service.forwarder(invalidVendorId)).toThrow("invalid vendor"); + }); + }); + + describe("preferences", () => { + it("should retrieve credential preferences bound to the user's account", () => { + const mockPreferences = { defaultType: "password" }; + providers.metadata!.preferences = jest.fn().mockReturnValue(mockPreferences); + + const result = service.preferences({ account$: of(account) }); + + expect(result).toBe(mockPreferences); + }); + }); + + describe("settings", () => { + it("should load user settings for account-bound profiles", () => { + const mockSettings = { value: { length: 12 } }; + const mockMetadata = { + id: "test", + profiles: { + [Profile.account]: { id: "accountProfile" }, + }, + } as unknown as GeneratorMetadata; + + providers.profile!.settings = jest.fn().mockReturnValue(mockSettings); + + const result = service.settings(mockMetadata, { account$: of(account) }); + + expect(result).toBe(mockSettings); + }); + + it("should load user settings for extension-bound profiles", () => { + const mockSettings = new BehaviorSubject({ value: { length: 12 } }); + const vendorId = Vendor.bitwarden; + const forwarderProfile = { + id: { forwarder: Bitwarden.id }, + site: Site.forwarder, + type: "extension", + }; + const mockMetadata = { + id: { forwarder: vendorId } as ForwarderExtensionId, + profiles: { + [Profile.account]: forwarderProfile, + }, + } as unknown as GeneratorMetadata; + + mockExtension.settings.mockReturnValue(mockSettings); + + const result = service.settings(mockMetadata, { account$: of(account) }); + + expect(result).toBe(mockSettings); + }); + + it("should log a panic when profile metadata is not found", () => { + const mockMetadata = { + id: "test", + profiles: {}, + } as unknown as GeneratorMetadata; + + expect(() => service.settings(mockMetadata, { account$: of(account) })).toThrow( + "failed to load settings; profile metadata not found", + ); + }); + }); + + describe("policy$", () => { + it("should retrieve policy constraints for a specific profile", async () => { + const mockConstraints = { minLength: 8 }; + const mockMetadata = { + id: "test", + profiles: { + [Profile.account]: { id: "accountProfile" }, + }, + } as unknown as GeneratorMetadata; + + providers.profile!.constraints$ = jest.fn().mockReturnValue(of(mockConstraints)); + + const result = await firstValueFrom(service.policy$(mockMetadata, { account$: of(account) })); + + expect(result).toEqual(mockConstraints); + }); + + it("should log a panic when profile metadata is not found for policy retrieval", () => { + const mockMetadata = { + id: "test", + profiles: {}, + } as unknown as GeneratorMetadata; + + expect(() => service.policy$(mockMetadata, { account$: of(account) })).toThrow( + "failed to load policy; profile metadata not found", + ); + }); + }); +}); diff --git a/libs/tools/generator/core/src/services/default-credential-generator.service.ts b/libs/tools/generator/core/src/services/default-credential-generator.service.ts new file mode 100644 index 00000000000..453139d284c --- /dev/null +++ b/libs/tools/generator/core/src/services/default-credential-generator.service.ts @@ -0,0 +1,219 @@ +import { + ReplaySubject, + concatMap, + filter, + first, + map, + of, + share, + shareReplay, + switchAll, + switchMap, + takeUntil, + tap, + timer, + zip, +} from "rxjs"; + +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { BoundDependency, OnDependency } from "@bitwarden/common/tools/dependencies"; +import { VendorId } from "@bitwarden/common/tools/extension"; +import { SemanticLogger } from "@bitwarden/common/tools/log"; +import { SystemServiceProvider } from "@bitwarden/common/tools/providers"; +import { anyComplete, memoizedMap } from "@bitwarden/common/tools/rx"; +import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject"; + +import { CredentialGeneratorService } from "../abstractions"; +import { + CredentialAlgorithm, + Profile, + GeneratorMetadata, + GeneratorProfile, + isForwarderProfile, + toVendorId, + CredentialType, +} from "../metadata"; +import { CredentialGeneratorProviders } from "../providers"; +import { GenerateRequest } from "../types"; +import { isAlgorithmRequest, isTypeRequest } from "../types/metadata-request"; + +const ALGORITHM_CACHE_SIZE = 10; +const THREE_MINUTES = 3 * 60 * 1000; + +export class DefaultCredentialGeneratorService implements CredentialGeneratorService { + /** Instantiate the `DefaultCredentialGeneratorService`. + * @param provide application services required by the credential generator. + * @param system low-level services required by the credential generator. + */ + constructor( + private readonly provide: CredentialGeneratorProviders, + private readonly system: SystemServiceProvider, + ) { + this.log = system.log({ type: "DefaultCredentialGeneratorService" }); + } + + private readonly log: SemanticLogger; + + generate$(dependencies: OnDependency & BoundDependency<"account", Account>) { + const request$ = dependencies.on$.pipe(shareReplay({ refCount: true, bufferSize: 1 })); + const account$ = dependencies.account$.pipe(shareReplay({ refCount: true, bufferSize: 1 })); + + // load algorithm metadata + const metadata$ = request$.pipe( + switchMap((request) => { + if (isAlgorithmRequest(request)) { + return of(request.algorithm); + } else if (isTypeRequest(request)) { + return this.provide.metadata.preference$(request.type, { account$ }).pipe(first()); + } else { + this.log.panic(request, "algorithm or category required"); + } + }), + filter((algorithm): algorithm is CredentialAlgorithm => !!algorithm), + memoizedMap((algorithm) => this.provide.metadata.metadata(algorithm), { + size: ALGORITHM_CACHE_SIZE, + }), + shareReplay({ refCount: true, bufferSize: 1 }), + ); + + // load the active profile's settings + const settings$ = zip(request$, metadata$).pipe( + map( + ([request, metadata]) => + [{ ...request, profile: request.profile ?? Profile.account }, metadata] as const, + ), + memoizedMap( + ([request, metadata]) => { + const [profile, algorithm] = [request.profile, metadata.id]; + + // settings$ stays hot and buffers the most recent value in the cache + // for the next `request` + const settings$ = this.settings(metadata, { account$ }, profile).pipe( + tap(() => this.log.debug({ algorithm, profile }, "settings update received")), + share({ + connector: () => new ReplaySubject(1, THREE_MINUTES), + resetOnRefCountZero: () => timer(THREE_MINUTES), + }), + tap({ + subscribe: () => this.log.debug({ algorithm, profile }, "settings hot"), + complete: () => this.log.debug({ algorithm, profile }, "settings cold"), + }), + first(), + ); + + this.log.debug({ algorithm, profile }, "settings cached"); + return settings$; + }, + { key: ([request, metadata]) => `${metadata.id}:${request.profile}` }, + ), + switchAll(), + ); + + // load the algorithm's engine + const engine$ = metadata$.pipe( + memoizedMap( + (metadata) => { + const engine = metadata.engine.create(this.provide.generator); + + this.log.debug({ algorithm: metadata.id }, "engine cached"); + return engine; + }, + { size: ALGORITHM_CACHE_SIZE }, + ), + ); + + // generation proper + const generate$ = zip([request$, settings$, engine$]).pipe( + tap(([request]) => this.log.debug(request, "generating credential")), + concatMap(([request, settings, engine]) => engine.generate(request, settings)), + takeUntil(anyComplete([settings$])), + ); + + return generate$; + } + + algorithms$(type: CredentialType, dependencies: BoundDependency<"account", Account>) { + return this.provide.metadata + .algorithms$({ type }, dependencies) + .pipe(map((algorithms) => algorithms.map((a) => this.algorithm(a)))); + } + + algorithms(type: CredentialType | CredentialType[]) { + const types: CredentialType[] = Array.isArray(type) ? type : [type]; + const algorithms = types + .flatMap((type) => this.provide.metadata.algorithms({ type })) + .map((algorithm) => this.algorithm(algorithm)); + return algorithms; + } + + algorithm(id: CredentialAlgorithm) { + const metadata = this.provide.metadata.metadata(id); + if (!metadata) { + this.log.panic({ algorithm: id }, "invalid credential algorithm"); + } + + return metadata; + } + + forwarder(id: VendorId) { + const metadata = this.provide.metadata.metadata({ forwarder: id }); + if (!metadata) { + this.log.panic({ algorithm: id }, "invalid vendor"); + } + + return metadata; + } + + preferences(dependencies: BoundDependency<"account", Account>) { + return this.provide.metadata.preferences(dependencies); + } + + settings( + metadata: Readonly>, + dependencies: BoundDependency<"account", Account>, + profile: GeneratorProfile = Profile.account, + ) { + const activeProfile = metadata.profiles[profile]; + if (!activeProfile) { + this.log.panic( + { algorithm: metadata.id, profile }, + "failed to load settings; profile metadata not found", + ); + } + + let settings: UserStateSubject; + if (isForwarderProfile(activeProfile)) { + const vendor = toVendorId(metadata.id); + if (!vendor) { + this.log.panic( + { algorithm: metadata.id, profile }, + "failed to load extension profile; vendor not specified", + ); + } + + this.log.info({ profile, vendor, site: activeProfile.site }, "loading extension profile"); + settings = this.system.extension.settings(activeProfile, vendor, dependencies); + } else { + this.log.info({ profile, algorithm: metadata.id }, "loading generator profile"); + settings = this.provide.profile.settings(activeProfile, dependencies); + } + + return settings; + } + + policy$( + metadata: Readonly>, + dependencies: BoundDependency<"account", Account>, + profile: GeneratorProfile = Profile.account, + ) { + const activeProfile = metadata.profiles[profile]; + if (!activeProfile) { + this.log.panic( + { algorithm: metadata.id, profile }, + "failed to load policy; profile metadata not found", + ); + } + + return this.provide.profile.constraints$(activeProfile, dependencies); + } +} diff --git a/libs/tools/generator/core/src/services/default-generator.service.spec.ts b/libs/tools/generator/core/src/services/default-generator.service.spec.ts index eb9642a9417..a0f9342fa28 100644 --- a/libs/tools/generator/core/src/services/default-generator.service.spec.ts +++ b/libs/tools/generator/core/src/services/default-generator.service.spec.ts @@ -18,7 +18,7 @@ import { DefaultGeneratorService } from "./default-generator.service"; function mockPolicyService(config?: { state?: BehaviorSubject }) { const service = mock(); - const stateValue = config?.state ?? new BehaviorSubject([null]); + const stateValue = config?.state ?? new BehaviorSubject([]); service.policiesByType$.mockReturnValue(stateValue); return service; @@ -119,22 +119,22 @@ describe("Password generator service", () => { it("should update the evaluator when the password generator policy changes", async () => { // set up dependencies - const state = new BehaviorSubject([null]); + const state = new BehaviorSubject([]); const policy = mockPolicyService({ state }); const strategy = mockGeneratorStrategy(); const service = new DefaultGeneratorService(strategy, policy); // model responses for the observable update. The map is called multiple times, // and the array shift ensures reference equality is maintained. - const firstEvaluator = mock>(); - const secondEvaluator = mock>(); + const firstEvaluator: PolicyEvaluator = mock>(); + const secondEvaluator: PolicyEvaluator = mock>(); const evaluators = [firstEvaluator, secondEvaluator]; - strategy.toEvaluator.mockReturnValueOnce(pipe(map(() => evaluators.shift()))); + strategy.toEvaluator.mockReturnValueOnce(pipe(map(() => evaluators.shift()!))); // act const evaluator$ = service.evaluator$(SomeUser); const firstResult = await firstValueFrom(evaluator$); - state.next([null]); + state.next([]); const secondResult = await firstValueFrom(evaluator$); // assert diff --git a/libs/tools/generator/core/src/services/index.ts b/libs/tools/generator/core/src/services/index.ts index d7184f684ae..57094e28ddd 100644 --- a/libs/tools/generator/core/src/services/index.ts +++ b/libs/tools/generator/core/src/services/index.ts @@ -1,2 +1,2 @@ export { DefaultGeneratorService } from "./default-generator.service"; -export { CredentialGeneratorService } from "./credential-generator.service"; +export { DefaultCredentialGeneratorService } from "./default-credential-generator.service"; diff --git a/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts b/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts index 83ed3b0d14e..ebeacef81e8 100644 --- a/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/eff-username-generator-strategy.ts @@ -2,7 +2,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { StateProvider } from "@bitwarden/common/platform/state"; import { GeneratorStrategy } from "../abstractions"; -import { DefaultEffUsernameOptions, UsernameDigits } from "../data"; +import { DefaultEffUsernameOptions } from "../data"; import { UsernameRandomizer } from "../engine"; import { newDefaultEvaluator } from "../rx"; import { EffUsernameGenerationOptions, NoPolicy } from "../types"; @@ -10,6 +10,11 @@ import { observe$PerUserId, sharedStateByUserId } from "../util"; import { EFF_USERNAME_SETTINGS } from "./storage"; +const UsernameDigits = Object.freeze({ + enabled: 4, + disabled: 0, +}); + /** Strategy for creating usernames from the EFF wordlist */ export class EffUsernameGeneratorStrategy implements GeneratorStrategy diff --git a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts index 5abcea82493..ee521d753ae 100644 --- a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts +++ b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.spec.ts @@ -8,7 +8,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { StateProvider } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; -import { DefaultPassphraseGenerationOptions, Policies } from "../data"; +import { DefaultPassphraseGenerationOptions } from "../data"; import { PasswordRandomizer } from "../engine"; import { PassphraseGeneratorOptionsEvaluator } from "../policies"; @@ -20,7 +20,7 @@ const SomeUser = "some user" as UserId; describe("Passphrase generation strategy", () => { describe("toEvaluator()", () => { it("should map to the policy evaluator", async () => { - const strategy = new PassphraseGeneratorStrategy(null, null); + const strategy = new PassphraseGeneratorStrategy(null!, null!); const policy = mock({ type: PolicyType.PasswordGenerator, data: { @@ -44,13 +44,18 @@ describe("Passphrase generation strategy", () => { it.each([[[]], [null], [undefined]])( "should map `%p` to a disabled password policy evaluator", async (policies) => { - const strategy = new PassphraseGeneratorStrategy(null, null); + const strategy = new PassphraseGeneratorStrategy(null!, null!); - const evaluator$ = of(policies).pipe(strategy.toEvaluator()); + // this case tests when the type system is subverted + const evaluator$ = of(policies!).pipe(strategy.toEvaluator()); const evaluator = await firstValueFrom(evaluator$); expect(evaluator).toBeInstanceOf(PassphraseGeneratorOptionsEvaluator); - expect(evaluator.policy).toMatchObject(Policies.Passphrase.disabledValue); + expect(evaluator.policy).toMatchObject({ + minNumberWords: 0, + capitalize: false, + includeNumber: false, + }); }, ); }); @@ -58,7 +63,7 @@ describe("Passphrase generation strategy", () => { describe("durableState", () => { it("should use password settings key", () => { const provider = mock(); - const strategy = new PassphraseGeneratorStrategy(null, provider); + const strategy = new PassphraseGeneratorStrategy(null!, provider); strategy.durableState(SomeUser); @@ -68,7 +73,7 @@ describe("Passphrase generation strategy", () => { describe("defaults$", () => { it("should return the default subaddress options", async () => { - const strategy = new PassphraseGeneratorStrategy(null, null); + const strategy = new PassphraseGeneratorStrategy(null!, null!); const result = await firstValueFrom(strategy.defaults$(SomeUser)); @@ -78,7 +83,7 @@ describe("Passphrase generation strategy", () => { describe("policy", () => { it("should use password generator policy", () => { - const strategy = new PassphraseGeneratorStrategy(null, null); + const strategy = new PassphraseGeneratorStrategy(null!, null!); expect(strategy.policy).toBe(PolicyType.PasswordGenerator); }); @@ -95,7 +100,7 @@ describe("Passphrase generation strategy", () => { }); it("should map options", async () => { - const strategy = new PassphraseGeneratorStrategy(randomizer, null); + const strategy = new PassphraseGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ numWords: 6, @@ -114,7 +119,7 @@ describe("Passphrase generation strategy", () => { }); it("should default numWords", async () => { - const strategy = new PassphraseGeneratorStrategy(randomizer, null); + const strategy = new PassphraseGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ capitalize: true, @@ -132,7 +137,7 @@ describe("Passphrase generation strategy", () => { }); it("should default capitalize", async () => { - const strategy = new PassphraseGeneratorStrategy(randomizer, null); + const strategy = new PassphraseGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ numWords: 6, @@ -150,7 +155,7 @@ describe("Passphrase generation strategy", () => { }); it("should default includeNumber", async () => { - const strategy = new PassphraseGeneratorStrategy(randomizer, null); + const strategy = new PassphraseGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ numWords: 6, @@ -168,7 +173,7 @@ describe("Passphrase generation strategy", () => { }); it("should default wordSeparator", async () => { - const strategy = new PassphraseGeneratorStrategy(randomizer, null); + const strategy = new PassphraseGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ numWords: 6, diff --git a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts index 759f065b2ff..374df84a5bd 100644 --- a/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/passphrase-generator-strategy.ts @@ -4,8 +4,9 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { StateProvider } from "@bitwarden/common/platform/state"; import { GeneratorStrategy } from "../abstractions"; -import { DefaultPassphraseGenerationOptions, Policies } from "../data"; +import { DefaultPassphraseGenerationOptions } from "../data"; import { PasswordRandomizer } from "../engine"; +import { PassphraseGeneratorOptionsEvaluator, passphraseLeastPrivilege } from "../policies"; import { mapPolicyToEvaluator } from "../rx"; import { PassphraseGenerationOptions, PassphraseGeneratorPolicy } from "../types"; import { observe$PerUserId, optionsToEffWordListRequest, sharedStateByUserId } from "../util"; @@ -30,7 +31,16 @@ export class PassphraseGeneratorStrategy defaults$ = observe$PerUserId(() => DefaultPassphraseGenerationOptions); readonly policy = PolicyType.PasswordGenerator; toEvaluator() { - return mapPolicyToEvaluator(Policies.Passphrase); + return mapPolicyToEvaluator({ + type: PolicyType.PasswordGenerator, + disabledValue: Object.freeze({ + minNumberWords: 0, + capitalize: false, + includeNumber: false, + }), + combine: passphraseLeastPrivilege, + createEvaluator: (policy) => new PassphraseGeneratorOptionsEvaluator(policy), + }); } // algorithm diff --git a/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts b/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts index 928e0b0dc8b..94e7c16be28 100644 --- a/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts +++ b/libs/tools/generator/core/src/strategies/password-generator-strategy.spec.ts @@ -8,7 +8,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { StateProvider } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; -import { DefaultPasswordGenerationOptions, Policies } from "../data"; +import { DefaultPasswordGenerationOptions } from "../data"; import { PasswordRandomizer } from "../engine"; import { PasswordGeneratorOptionsEvaluator } from "../policies"; @@ -20,7 +20,7 @@ const SomeUser = "some user" as UserId; describe("Password generation strategy", () => { describe("toEvaluator()", () => { it("should map to a password policy evaluator", async () => { - const strategy = new PasswordGeneratorStrategy(null, null); + const strategy = new PasswordGeneratorStrategy(null!, null!); const policy = mock({ type: PolicyType.PasswordGenerator, data: { @@ -52,13 +52,22 @@ describe("Password generation strategy", () => { it.each([[[]], [null], [undefined]])( "should map `%p` to a disabled password policy evaluator", async (policies) => { - const strategy = new PasswordGeneratorStrategy(null, null); + const strategy = new PasswordGeneratorStrategy(null!, null!); - const evaluator$ = of(policies).pipe(strategy.toEvaluator()); + // this case tests when the type system is subverted + const evaluator$ = of(policies!).pipe(strategy.toEvaluator()); const evaluator = await firstValueFrom(evaluator$); expect(evaluator).toBeInstanceOf(PasswordGeneratorOptionsEvaluator); - expect(evaluator.policy).toMatchObject(Policies.Password.disabledValue); + expect(evaluator.policy).toMatchObject({ + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, + }); }, ); }); @@ -66,7 +75,7 @@ describe("Password generation strategy", () => { describe("durableState", () => { it("should use password settings key", () => { const provider = mock(); - const strategy = new PasswordGeneratorStrategy(null, provider); + const strategy = new PasswordGeneratorStrategy(null!, provider); strategy.durableState(SomeUser); @@ -76,7 +85,7 @@ describe("Password generation strategy", () => { describe("defaults$", () => { it("should return the default subaddress options", async () => { - const strategy = new PasswordGeneratorStrategy(null, null); + const strategy = new PasswordGeneratorStrategy(null!, null!); const result = await firstValueFrom(strategy.defaults$(SomeUser)); @@ -86,7 +95,7 @@ describe("Password generation strategy", () => { describe("policy", () => { it("should use password generator policy", () => { - const strategy = new PasswordGeneratorStrategy(null, null); + const strategy = new PasswordGeneratorStrategy(null!, null!); expect(strategy.policy).toBe(PolicyType.PasswordGenerator); }); @@ -103,7 +112,7 @@ describe("Password generation strategy", () => { }); it("should map options", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 20, @@ -130,7 +139,7 @@ describe("Password generation strategy", () => { }); it("should disable uppercase", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 3, @@ -157,7 +166,7 @@ describe("Password generation strategy", () => { }); it("should disable lowercase", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 3, @@ -184,7 +193,7 @@ describe("Password generation strategy", () => { }); it("should disable digits", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 3, @@ -211,7 +220,7 @@ describe("Password generation strategy", () => { }); it("should disable special", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 3, @@ -238,7 +247,7 @@ describe("Password generation strategy", () => { }); it("should override length with minimums", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 20, @@ -265,7 +274,7 @@ describe("Password generation strategy", () => { }); it("should default uppercase", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 2, @@ -291,7 +300,7 @@ describe("Password generation strategy", () => { }); it("should default lowercase", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 0, @@ -317,7 +326,7 @@ describe("Password generation strategy", () => { }); it("should default number", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 0, @@ -343,7 +352,7 @@ describe("Password generation strategy", () => { }); it("should default special", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 0, @@ -369,7 +378,7 @@ describe("Password generation strategy", () => { }); it("should default minUppercase", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 0, @@ -395,7 +404,7 @@ describe("Password generation strategy", () => { }); it("should default minLowercase", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 0, @@ -421,7 +430,7 @@ describe("Password generation strategy", () => { }); it("should default minNumber", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 0, @@ -447,7 +456,7 @@ describe("Password generation strategy", () => { }); it("should default minSpecial", async () => { - const strategy = new PasswordGeneratorStrategy(randomizer, null); + const strategy = new PasswordGeneratorStrategy(randomizer, null!); const result = await strategy.generate({ length: 0, diff --git a/libs/tools/generator/core/src/strategies/password-generator-strategy.ts b/libs/tools/generator/core/src/strategies/password-generator-strategy.ts index 9ff8a3d88b0..1a5070901c2 100644 --- a/libs/tools/generator/core/src/strategies/password-generator-strategy.ts +++ b/libs/tools/generator/core/src/strategies/password-generator-strategy.ts @@ -2,8 +2,9 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { StateProvider } from "@bitwarden/common/platform/state"; import { GeneratorStrategy } from "../abstractions"; -import { Policies, DefaultPasswordGenerationOptions } from "../data"; +import { DefaultPasswordGenerationOptions } from "../data"; import { PasswordRandomizer } from "../engine"; +import { PasswordGeneratorOptionsEvaluator, passwordLeastPrivilege } from "../policies"; import { mapPolicyToEvaluator } from "../rx"; import { PasswordGenerationOptions, PasswordGeneratorPolicy } from "../types"; import { observe$PerUserId, optionsToRandomAsciiRequest, sharedStateByUserId } from "../util"; @@ -27,7 +28,20 @@ export class PasswordGeneratorStrategy defaults$ = observe$PerUserId(() => DefaultPasswordGenerationOptions); readonly policy = PolicyType.PasswordGenerator; toEvaluator() { - return mapPolicyToEvaluator(Policies.Password); + return mapPolicyToEvaluator({ + type: PolicyType.PasswordGenerator, + disabledValue: { + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, + }, + combine: passwordLeastPrivilege, + createEvaluator: (policy) => new PasswordGeneratorOptionsEvaluator(policy), + }); } // algorithm diff --git a/libs/tools/generator/core/src/types/algorithm-info.ts b/libs/tools/generator/core/src/types/algorithm-info.ts new file mode 100644 index 00000000000..a3db03600b6 --- /dev/null +++ b/libs/tools/generator/core/src/types/algorithm-info.ts @@ -0,0 +1,50 @@ +import { CredentialAlgorithm, CredentialType } from "../metadata"; + +// FIXME: deprecate or delete `AlgorithmInfo` once a better translation +// strategy is identified. +export type AlgorithmInfo = { + /** Uniquely identifies the credential configuration + * @example + * // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)` + * // to pattern test whether the credential describes a forwarder algorithm + * const meta : CredentialGeneratorInfo = // ... + * const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {}; + */ + id: CredentialAlgorithm; + + /** The kind of credential generated by this configuration */ + type: CredentialType; + + /** Localized algorithm name */ + name: string; + + /* Localized generate button label */ + generate: string; + + /** Localized "credential generated" informational message */ + onGeneratedMessage: string; + + /* Localized copy button label */ + copy: string; + + /* Localized dialog button label */ + useGeneratedValue: string; + + /* Localized generated value label */ + credentialType: string; + + /** Localized algorithm description */ + description?: string; + + /** When true, credential generation must be explicitly requested. + * @remarks this property is useful when credential generation + * carries side effects, such as configuring a service external + * to Bitwarden. + */ + onlyOnRequest: boolean; + + /** Well-known fields to display on the options panel or collect from the environment. + * @remarks: at present, this is only used by forwarders + */ + request: readonly string[]; +}; diff --git a/libs/tools/generator/core/src/types/credential-generator-configuration.ts b/libs/tools/generator/core/src/types/credential-generator-configuration.ts deleted file mode 100644 index 36b0f3046a9..00000000000 --- a/libs/tools/generator/core/src/types/credential-generator-configuration.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { UserKeyDefinition } from "@bitwarden/common/platform/state"; -import { RestClient } from "@bitwarden/common/tools/integration/rpc"; -import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; -import { Constraints } from "@bitwarden/common/tools/types"; - -import { Randomizer } from "../abstractions"; -import { CredentialAlgorithm, CredentialCategory, PolicyConfiguration } from "../types"; - -import { CredentialGenerator } from "./credential-generator"; - -export type GeneratorDependencyProvider = { - randomizer: Randomizer; - client: RestClient; - i18nService: I18nService; -}; - -export type AlgorithmInfo = { - /** Uniquely identifies the credential configuration - * @example - * // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)` - * // to pattern test whether the credential describes a forwarder algorithm - * const meta : CredentialGeneratorInfo = // ... - * const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {}; - */ - id: CredentialAlgorithm; - - /** The kind of credential generated by this configuration */ - category: CredentialCategory; - - /** Localized algorithm name */ - name: string; - - /* Localized generate button label */ - generate: string; - - /** Localized "credential generated" informational message */ - onGeneratedMessage: string; - - /* Localized copy button label */ - copy: string; - - /* Localized dialog button label */ - useGeneratedValue: string; - - /* Localized generated value label */ - credentialType: string; - - /** Localized algorithm description */ - description?: string; - - /** When true, credential generation must be explicitly requested. - * @remarks this property is useful when credential generation - * carries side effects, such as configuring a service external - * to Bitwarden. - */ - onlyOnRequest: boolean; - - /** Well-known fields to display on the options panel or collect from the environment. - * @remarks: at present, this is only used by forwarders - */ - request: readonly string[]; -}; - -/** Credential generator metadata common across credential generators */ -export type CredentialGeneratorInfo = { - /** Uniquely identifies the credential configuration - * @example - * // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)` - * // to pattern test whether the credential describes a forwarder algorithm - * const meta : CredentialGeneratorInfo = // ... - * const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {}; - */ - id: CredentialAlgorithm; - - /** The kind of credential generated by this configuration */ - category: CredentialCategory; - - /** Localization key for the credential name */ - nameKey: string; - - /** Localization key for the credential description*/ - descriptionKey?: string; - - /** Localization key for the generate command label */ - generateKey: string; - - /** Localization key for the copy button label */ - copyKey: string; - - /** Localization key for the "credential generated" informational message */ - onGeneratedMessageKey: string; - - /** Localized "use generated credential" button label */ - useGeneratedValueKey: string; - - /** Localization key for describing the kind of credential generated - * by this generator. - */ - credentialTypeKey: string; - - /** When true, credential generation must be explicitly requested. - * @remarks this property is useful when credential generation - * carries side effects, such as configuring a service external - * to Bitwarden. - */ - onlyOnRequest: boolean; - - /** Well-known fields to display on the options panel or collect from the environment. - * @remarks: at present, this is only used by forwarders - */ - request: readonly string[]; -}; - -/** Credential generator metadata that relies upon typed setting and policy definitions. - * @example - * // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)` - * // to pattern test whether the credential describes a forwarder algorithm - * const meta : CredentialGeneratorInfo = // ... - * const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {}; - */ -export type CredentialGeneratorConfiguration = CredentialGeneratorInfo & { - /** An algorithm that generates credentials when ran. */ - engine: { - /** Factory for the generator - */ - // FIXME: note that this erases the engine's type so that credentials are - // generated uniformly. This property needs to be maintained for - // the credential generator, but engine configurations should return - // the underlying type. `create` may be able to do double-duty w/ an - // engine definition if `CredentialGenerator` can be made covariant. - create: (randomizer: GeneratorDependencyProvider) => CredentialGenerator; - }; - /** Defines the stored parameters for credential generation */ - settings: { - /** value used when an account's settings haven't been initialized - * @deprecated use `ObjectKey.initial` for your desired storage property instead - */ - initial: Readonly>; - - /** Application-global constraints that apply to account settings */ - constraints: Constraints; - - /** storage location for account-global settings */ - account: UserKeyDefinition | ObjectKey; - - /** storage location for *plaintext* settings imports */ - import?: UserKeyDefinition | ObjectKey, Settings>; - }; - - /** defines how to construct policy for this settings instance */ - policy: PolicyConfiguration; -}; diff --git a/libs/tools/generator/core/src/types/credential-preference.ts b/libs/tools/generator/core/src/types/credential-preference.ts new file mode 100644 index 00000000000..957299e5c31 --- /dev/null +++ b/libs/tools/generator/core/src/types/credential-preference.ts @@ -0,0 +1,10 @@ +import { CredentialAlgorithm, CredentialType } from "../metadata"; + +/** The kind of credential to generate using a compound configuration. */ +// FIXME: extend the preferences to include a preferred forwarder +export type CredentialPreference = { + [Key in CredentialType]: { + algorithm: CredentialAlgorithm; + updated: Date; + }; +}; diff --git a/libs/tools/generator/core/src/types/forwarder-options.ts b/libs/tools/generator/core/src/types/forwarder-options.ts index 7ba04da99a8..06084261283 100644 --- a/libs/tools/generator/core/src/types/forwarder-options.ts +++ b/libs/tools/generator/core/src/types/forwarder-options.ts @@ -7,32 +7,65 @@ import { import { EmailDomainSettings, EmailPrefixSettings } from "../engine"; +// FIXME: this type alias is in place for legacy support purposes; +// when replacing the forwarder implementation, eliminate `ForwarderId` and +// `IntegrationId`. The proper type is `VendorId`. /** Identifiers for email forwarding services. * @remarks These are used to select forwarder-specific options. * The must be kept in sync with the forwarder implementations. */ export type ForwarderId = IntegrationId; -/** Metadata format for email forwarding services. */ -export type ForwarderMetadata = { - /** The unique identifier for the forwarder. */ - id: ForwarderId; - - /** The name of the service the forwarder queries. */ - name: string; - - /** Whether the forwarder is valid for self-hosted instances of Bitwarden. */ - validForSelfHosted: boolean; -}; - -/** Options common to all forwarder APIs */ +/** Options common to all forwarder APIs + * @deprecated use {@link ForwarderOptions} instead. + */ export type ApiOptions = ApiSettings & IntegrationRequest; -/** Api configuration for forwarders that support self-hosted installations. */ +/** Api configuration for forwarders that support self-hosted installations. + * @deprecated use {@link ForwarderOptions} instead. + */ export type SelfHostedApiOptions = SelfHostedApiSettings & IntegrationRequest; -/** Api configuration for forwarders that support custom domains. */ +/** Api configuration for forwarders that support custom domains. + * @deprecated use {@link ForwarderOptions} instead. + */ export type EmailDomainOptions = EmailDomainSettings; -/** Api configuration for forwarders that support custom email parts. */ +/** Api configuration for forwarders that support custom email parts. + * @deprecated use {@link ForwarderOptions} instead. + */ export type EmailPrefixOptions = EmailDomainSettings & EmailPrefixSettings; + +/** These options are used by all forwarders; each forwarder uses a different set, + * as defined by `GeneratorMetadata.capabilities.fields`. + */ +export type ForwarderOptions = Partial< + { + /** bearer token that authenticates bitwarden to the forwarder. + * This is required to issue an API request. + */ + token: string; + + /** The base URL of the forwarder's API. + * When this is undefined or empty, the forwarder's default production API is used. + */ + baseUrl: string; + + /** The domain part of the generated email address. + * @remarks The domain should be authorized by the forwarder before + * submitting a request through bitwarden. + * @example If the domain is `domain.io` and the generated username + * is `jd`, then the generated email address will be `jd@domain.io` + */ + domain: string; + + /** A prefix joined to the generated email address' username. + * @example If the prefix is `foo`, the generated username is `bar`, + * and the domain is `domain.io`, then the generated email address is + * `foobar@domain.io`. + */ + prefix: string; + } & EmailDomainSettings & + EmailPrefixSettings & + SelfHostedApiSettings +>; diff --git a/libs/tools/generator/core/src/types/generate-request.ts b/libs/tools/generator/core/src/types/generate-request.ts index c7d5bf9c41c..9dbaaee12f6 100644 --- a/libs/tools/generator/core/src/types/generate-request.ts +++ b/libs/tools/generator/core/src/types/generate-request.ts @@ -1,6 +1,15 @@ +import { RequireExactlyOne } from "type-fest"; + +import { CredentialType, GeneratorProfile, CredentialAlgorithm } from "../metadata"; + /** Contextual information about the application state when a generator is invoked. */ -export type GenerateRequest = { +export type GenerateRequest = RequireExactlyOne< + { type: CredentialType; algorithm: CredentialAlgorithm }, + "type" | "algorithm" +> & { + profile?: GeneratorProfile; + /** Traces the origin of the generation request. This parameter is * copied to the generated credential. * diff --git a/libs/tools/generator/core/src/types/generated-credential.spec.ts b/libs/tools/generator/core/src/types/generated-credential.spec.ts index 6a498282fe3..3d8d2c9bd4d 100644 --- a/libs/tools/generator/core/src/types/generated-credential.spec.ts +++ b/libs/tools/generator/core/src/types/generated-credential.spec.ts @@ -1,40 +1,42 @@ -import { CredentialAlgorithm, GeneratedCredential } from "."; +import { Type } from "../metadata"; + +import { GeneratedCredential } from "./generated-credential"; describe("GeneratedCredential", () => { describe("constructor", () => { it("assigns credential", () => { - const result = new GeneratedCredential("example", "passphrase", new Date(100)); + const result = new GeneratedCredential("example", Type.password, new Date(100)); expect(result.credential).toEqual("example"); }); it("assigns category", () => { - const result = new GeneratedCredential("example", "passphrase", new Date(100)); + const result = new GeneratedCredential("example", Type.password, new Date(100)); - expect(result.category).toEqual("passphrase"); + expect(result.category).toEqual(Type.password); }); it("passes through date parameters", () => { - const result = new GeneratedCredential("example", "password", new Date(100)); + const result = new GeneratedCredential("example", Type.password, new Date(100)); expect(result.generationDate).toEqual(new Date(100)); }); it("converts numeric dates to Dates", () => { - const result = new GeneratedCredential("example", "password", 100); + const result = new GeneratedCredential("example", Type.password, 100); expect(result.generationDate).toEqual(new Date(100)); }); }); it("toJSON converts from a credential into a JSON object", () => { - const credential = new GeneratedCredential("example", "password", new Date(100)); + const credential = new GeneratedCredential("example", Type.password, new Date(100)); const result = credential.toJSON(); expect(result).toEqual({ credential: "example", - category: "password" as CredentialAlgorithm, + category: Type.password, generationDate: 100, }); }); @@ -42,7 +44,7 @@ describe("GeneratedCredential", () => { it("fromJSON converts Json objects into credentials", () => { const jsonValue = { credential: "example", - category: "password" as CredentialAlgorithm, + category: Type.password, generationDate: 100, }; diff --git a/libs/tools/generator/core/src/types/generated-credential.ts b/libs/tools/generator/core/src/types/generated-credential.ts index 99b864b9fd8..695e3866920 100644 --- a/libs/tools/generator/core/src/types/generated-credential.ts +++ b/libs/tools/generator/core/src/types/generated-credential.ts @@ -1,13 +1,13 @@ import { Jsonify } from "type-fest"; -import { CredentialAlgorithm } from "./generator-type"; +import { CredentialType } from "../metadata"; /** A credential generation result */ export class GeneratedCredential { /** * Instantiates a generated credential * @param credential The value of the generated credential (e.g. a password) - * @param category The kind of credential + * @param category The type of credential * @param generationDate The date that the credential was generated. * Numeric values should are interpreted using {@link Date.valueOf} * semantics. @@ -16,7 +16,9 @@ export class GeneratedCredential { */ constructor( readonly credential: string, - readonly category: CredentialAlgorithm, + // FIXME: create a way to migrate the data stored in `category` to a new `type` + // field. The hard part: This requires the migration occur post-decryption. + readonly category: CredentialType, generationDate: Date | number, readonly source?: string, readonly website?: string, diff --git a/libs/tools/generator/core/src/types/generator-type.ts b/libs/tools/generator/core/src/types/generator-type.ts deleted file mode 100644 index c75e4329610..00000000000 --- a/libs/tools/generator/core/src/types/generator-type.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { VendorId } from "@bitwarden/common/tools/extension"; -import { IntegrationId } from "@bitwarden/common/tools/integration"; - -import { EmailAlgorithms, PasswordAlgorithms, UsernameAlgorithms } from "../data/generator-types"; -import { AlgorithmsByType, CredentialType } from "../metadata"; - -/** A type of password that may be generated by the credential generator. */ -export type PasswordAlgorithm = (typeof PasswordAlgorithms)[number]; - -/** A type of username that may be generated by the credential generator. */ -export type UsernameAlgorithm = (typeof UsernameAlgorithms)[number]; - -/** A type of email address that may be generated by the credential generator. */ -export type EmailAlgorithm = (typeof EmailAlgorithms)[number]; - -export type ForwarderIntegration = { forwarder: IntegrationId & VendorId }; - -/** Returns true when the input algorithm is a forwarder integration. */ -export function isForwarderIntegration( - algorithm: CredentialAlgorithm, -): algorithm is ForwarderIntegration { - return algorithm && typeof algorithm === "object" && "forwarder" in algorithm; -} - -export function isSameAlgorithm(lhs: CredentialAlgorithm, rhs: CredentialAlgorithm) { - if (lhs === rhs) { - return true; - } else if (isForwarderIntegration(lhs) && isForwarderIntegration(rhs)) { - return lhs.forwarder === rhs.forwarder; - } else { - return false; - } -} - -/** A type of credential that may be generated by the credential generator. */ -export type CredentialAlgorithm = - | PasswordAlgorithm - | UsernameAlgorithm - | EmailAlgorithm - | ForwarderIntegration; - -/** Compound credential types supported by the credential generator. */ -export const CredentialCategories = Object.freeze({ - /** Lists algorithms in the "password" credential category */ - password: PasswordAlgorithms as Readonly, - - /** Lists algorithms in the "username" credential category */ - username: UsernameAlgorithms as Readonly, - - /** Lists algorithms in the "email" credential category */ - email: EmailAlgorithms as Readonly<(EmailAlgorithm | ForwarderIntegration)[]>, -}); - -/** Returns true when the input algorithm is a password algorithm. */ -export function isPasswordAlgorithm( - algorithm: CredentialAlgorithm, -): algorithm is PasswordAlgorithm { - return PasswordAlgorithms.includes(algorithm as any); -} - -/** Returns true when the input algorithm is a username algorithm. */ -export function isUsernameAlgorithm( - algorithm: CredentialAlgorithm, -): algorithm is UsernameAlgorithm { - return UsernameAlgorithms.includes(algorithm as any); -} - -/** Returns true when the input algorithm is an email algorithm. */ -export function isEmailAlgorithm(algorithm: CredentialAlgorithm): algorithm is EmailAlgorithm { - return EmailAlgorithms.includes(algorithm as any) || isForwarderIntegration(algorithm); -} - -/** A type of compound credential that may be generated by the credential generator. */ -export type CredentialCategory = keyof typeof CredentialCategories; - -/** The kind of credential to generate using a compound configuration. */ -// FIXME: extend the preferences to include a preferred forwarder -export type CredentialPreference = { - [Key in CredentialType & CredentialCategory]: { - algorithm: CredentialAlgorithm & (typeof AlgorithmsByType)[Key][number]; - updated: Date; - }; -}; diff --git a/libs/tools/generator/core/src/types/index.ts b/libs/tools/generator/core/src/types/index.ts index 3e392257b0c..f6460342b09 100644 --- a/libs/tools/generator/core/src/types/index.ts +++ b/libs/tools/generator/core/src/types/index.ts @@ -1,16 +1,14 @@ -import { EmailAlgorithm, PasswordAlgorithm, UsernameAlgorithm } from "./generator-type"; - export * from "./boundary"; export * from "./catchall-generator-options"; export * from "./credential-generator"; -export * from "./credential-generator-configuration"; +export * from "./algorithm-info"; export * from "./eff-username-generator-options"; export * from "./forwarder-options"; export * from "./generate-request"; export * from "./generator-constraints"; export * from "./generated-credential"; export * from "./generator-options"; -export * from "./generator-type"; +export * from "./credential-preference"; export * from "./no-policy"; export * from "./passphrase-generation-options"; export * from "./passphrase-generator-policy"; @@ -19,13 +17,3 @@ export * from "./password-generator-policy"; export * from "./policy-configuration"; export * from "./subaddress-generator-options"; export * from "./word-options"; - -/** Provided for backwards compatibility only. - * @deprecated Use one of the Algorithm types instead. - */ -export type GeneratorType = PasswordAlgorithm | UsernameAlgorithm | EmailAlgorithm; - -/** Provided for backwards compatibility only. - * @deprecated Use one of the Algorithm types instead. - */ -export type PasswordType = PasswordAlgorithm; diff --git a/libs/tools/generator/core/src/types/policy-configuration.ts b/libs/tools/generator/core/src/types/policy-configuration.ts index 07ded886609..e4db437575a 100644 --- a/libs/tools/generator/core/src/types/policy-configuration.ts +++ b/libs/tools/generator/core/src/types/policy-configuration.ts @@ -3,8 +3,6 @@ import { Policy as AdminPolicy } from "@bitwarden/common/admin-console/models/do import { PolicyEvaluator } from "../abstractions"; -import { GeneratorConstraints } from "./generator-constraints"; - /** Determines how to construct a password generator policy */ export type PolicyConfiguration = { type: PolicyType; @@ -22,15 +20,4 @@ export type PolicyConfiguration = { * Use `toConstraints` instead. */ createEvaluator: (policy: Policy) => PolicyEvaluator; - - /** Converts policy service data into actionable policy constraints. - * - * @param policy - the policy to map into policy constraints. - * @param email - the default email to extend. - * - * @remarks this version includes constraints needed for the reactive forms; - * it was introduced so that the constraints can be incrementally introduced - * as the new UI is built. - */ - toConstraints: (policy: Policy, email: string) => GeneratorConstraints; }; diff --git a/libs/tools/generator/core/src/util.spec.ts b/libs/tools/generator/core/src/util.spec.ts index 8ed95a9f268..5aeaeb3c67e 100644 --- a/libs/tools/generator/core/src/util.spec.ts +++ b/libs/tools/generator/core/src/util.spec.ts @@ -1,5 +1,6 @@ import { DefaultPassphraseGenerationOptions } from "./data"; -import { optionsToEffWordListRequest, optionsToRandomAsciiRequest, sum } from "./util"; +import { GeneratorConstraints, PassphraseGenerationOptions } from "./types"; +import { optionsToEffWordListRequest, optionsToRandomAsciiRequest, sum, equivalent } from "./util"; describe("sum", () => { it("returns 0 when the list is empty", () => { @@ -424,3 +425,90 @@ describe("optionsToEffWordListRequest", () => { }); }); }); + +describe("equivalent", () => { + // constructs a partial constraints object; only the properties compared + // by `equivalent` are included. + function createConstraints( + policyInEffect: boolean, + numWordsMin?: number, + capitalize?: boolean, + ): GeneratorConstraints { + return { + constraints: { + policyInEffect, + numWords: numWordsMin !== undefined ? { min: numWordsMin } : undefined, + capitalize: capitalize !== undefined ? { requiredValue: capitalize } : undefined, + }, + } as unknown as GeneratorConstraints; + } + + it("should return true for identical constraints", () => { + const lhs = createConstraints(false, 3, true); + const rhs = createConstraints(false, 3, true); + + expect(equivalent(lhs, rhs)).toBe(true); + }); + + it("should return false when policy effects differ", () => { + const lhs = createConstraints(true, 3); + const rhs = createConstraints(false, 3); + + expect(equivalent(lhs, rhs)).toBe(false); + }); + + it("should return false when constraint values differ", () => { + const lhs = createConstraints(false, 3); + const rhs = createConstraints(false, 4); + + expect(equivalent(lhs, rhs)).toBe(false); + }); + + it("should return false when one has additional constraints", () => { + const lhs = createConstraints(false, 3, true); + const rhs = createConstraints(false, 3); + + expect(equivalent(lhs, rhs)).toBe(false); + }); + + it("should handle undefined constraints", () => { + const lhs = createConstraints(false); + const rhs = createConstraints(false); + + expect(equivalent(lhs, rhs)).toBe(true); + }); + + it("should handle empty constraint objects", () => { + const lhs = { + constraints: { + policyInEffect: false, + numWords: {}, + }, + } as unknown as GeneratorConstraints; + const rhs = { + constraints: { + policyInEffect: false, + numWords: {}, + }, + } as unknown as GeneratorConstraints; + + expect(equivalent(lhs, rhs)).toBe(true); + }); + + it("should return false when inner constraint properties differ", () => { + const lhs = { + constraints: { + policyInEffect: false, + numWords: { min: 3, max: 5 }, + }, + } as any; + const rhs = { + constraints: { + policyInEffect: false, + numWords: { min: 3, max: 6 }, + }, + } as unknown as GeneratorConstraints; + + expect(equivalent(lhs, rhs)).toBe(false); + }); +}); diff --git a/libs/tools/generator/core/src/util.ts b/libs/tools/generator/core/src/util.ts index 4b6041ffeba..62f749faf56 100644 --- a/libs/tools/generator/core/src/util.ts +++ b/libs/tools/generator/core/src/util.ts @@ -14,7 +14,11 @@ import { DefaultPassphraseGenerationOptions, DefaultPasswordGenerationOptions, } from "./data"; -import { PassphraseGenerationOptions, PasswordGenerationOptions } from "./types"; +import { + PassphraseGenerationOptions, + PasswordGenerationOptions, + GeneratorConstraints, +} from "./types"; /** construct a method that outputs a copy of `defaultValue` as an observable. */ export function observe$PerUserId( @@ -135,3 +139,30 @@ export function optionsToEffWordListRequest(options: PassphraseGenerationOptions return request; } + +export function equivalent(lhs: GeneratorConstraints, rhs: GeneratorConstraints): boolean { + if (lhs.constraints.policyInEffect !== rhs.constraints.policyInEffect) { + return false; + } + + // safe because `Constraints` shares keys with `T` + const keys = Object.keys(lhs.constraints) as (keyof T)[]; + + // use `for` loop so that `equivalent` can return as soon as the constraints + // differ. Using `array.xyz` would evaluate the whole key list eagerly + for (const k of keys) { + if (!(k in rhs.constraints)) { + return false; + } + + const lhsConstraints: any = lhs.constraints[k] ?? {}; + const rhsConstraints: any = rhs.constraints[k] ?? {}; + + const innerKeys = Object.keys(lhsConstraints); + if (innerKeys.some((k) => lhsConstraints[k] !== rhsConstraints[k])) { + return false; + } + } + + return true; +} diff --git a/libs/tools/generator/core/tsconfig.json b/libs/tools/generator/core/tsconfig.json index a95b588686f..303f913ba23 100644 --- a/libs/tools/generator/core/tsconfig.json +++ b/libs/tools/generator/core/tsconfig.json @@ -8,10 +8,6 @@ "@bitwarden/key-management": ["../../../key-management/src"] } }, - "include": [ - "src", - "../extensions/src/history/generator-history.abstraction.ts", - "../extensions/src/navigation/generator-navigation.service.abstraction.ts" - ], + "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/generator/extensions/history/src/generated-credential.spec.ts b/libs/tools/generator/extensions/history/src/generated-credential.spec.ts index 846f2ee96be..26a48cb83ea 100644 --- a/libs/tools/generator/extensions/history/src/generated-credential.spec.ts +++ b/libs/tools/generator/extensions/history/src/generated-credential.spec.ts @@ -1,40 +1,42 @@ -import { GeneratorCategory, GeneratedCredential } from "."; +import { Type } from "@bitwarden/generator-core"; + +import { GeneratedCredential } from "."; describe("GeneratedCredential", () => { describe("constructor", () => { it("assigns credential", () => { - const result = new GeneratedCredential("example", "passphrase", new Date(100)); + const result = new GeneratedCredential("example", Type.password, new Date(100)); expect(result.credential).toEqual("example"); }); it("assigns category", () => { - const result = new GeneratedCredential("example", "passphrase", new Date(100)); + const result = new GeneratedCredential("example", Type.password, new Date(100)); - expect(result.category).toEqual("passphrase"); + expect(result.category).toEqual(Type.password); }); it("passes through date parameters", () => { - const result = new GeneratedCredential("example", "password", new Date(100)); + const result = new GeneratedCredential("example", Type.password, new Date(100)); expect(result.generationDate).toEqual(new Date(100)); }); it("converts numeric dates to Dates", () => { - const result = new GeneratedCredential("example", "password", 100); + const result = new GeneratedCredential("example", Type.password, 100); expect(result.generationDate).toEqual(new Date(100)); }); }); it("toJSON converts from a credential into a JSON object", () => { - const credential = new GeneratedCredential("example", "password", new Date(100)); + const credential = new GeneratedCredential("example", Type.password, new Date(100)); const result = credential.toJSON(); expect(result).toEqual({ credential: "example", - category: "password" as GeneratorCategory, + category: Type.password, generationDate: 100, }); }); @@ -42,7 +44,7 @@ describe("GeneratedCredential", () => { it("fromJSON converts Json objects into credentials", () => { const jsonValue = { credential: "example", - category: "password" as GeneratorCategory, + category: Type.password, generationDate: 100, }; @@ -51,7 +53,7 @@ describe("GeneratedCredential", () => { expect(result).toBeInstanceOf(GeneratedCredential); expect(result).toEqual({ credential: "example", - category: "password", + category: Type.password, generationDate: new Date(100), }); }); diff --git a/libs/tools/generator/extensions/history/src/generated-credential.ts b/libs/tools/generator/extensions/history/src/generated-credential.ts index 32efb752258..bec9e5ac7ee 100644 --- a/libs/tools/generator/extensions/history/src/generated-credential.ts +++ b/libs/tools/generator/extensions/history/src/generated-credential.ts @@ -1,6 +1,6 @@ import { Jsonify } from "type-fest"; -import { CredentialAlgorithm } from "@bitwarden/generator-core"; +import { CredentialType } from "@bitwarden/generator-core"; /** A credential generation result */ export class GeneratedCredential { @@ -14,7 +14,7 @@ export class GeneratedCredential { */ constructor( readonly credential: string, - readonly category: CredentialAlgorithm, + readonly category: CredentialType, generationDate: Date | number, ) { if (typeof generationDate === "number") { diff --git a/libs/tools/generator/extensions/history/src/generator-history.abstraction.ts b/libs/tools/generator/extensions/history/src/generator-history.abstraction.ts index 3b8a0e05a9e..3e3d4002be4 100644 --- a/libs/tools/generator/extensions/history/src/generator-history.abstraction.ts +++ b/libs/tools/generator/extensions/history/src/generator-history.abstraction.ts @@ -3,7 +3,7 @@ import { Observable } from "rxjs"; import { UserId } from "@bitwarden/common/types/guid"; -import { CredentialAlgorithm } from "@bitwarden/generator-core"; +import { CredentialType } from "@bitwarden/generator-core"; import { GeneratedCredential } from "./generated-credential"; @@ -29,7 +29,7 @@ export abstract class GeneratorHistoryService { track: ( userId: UserId, credential: string, - category: CredentialAlgorithm, + category: CredentialType, date?: Date, ) => Promise; diff --git a/libs/tools/generator/extensions/history/src/local-generator-history.service.spec.ts b/libs/tools/generator/extensions/history/src/local-generator-history.service.spec.ts index 3621b2c24a9..caff4234386 100644 --- a/libs/tools/generator/extensions/history/src/local-generator-history.service.spec.ts +++ b/libs/tools/generator/extensions/history/src/local-generator-history.service.spec.ts @@ -7,6 +7,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; +import { Type } from "@bitwarden/generator-core"; import { KeyService } from "@bitwarden/key-management"; import { FakeStateProvider, awaitAsync, mockAccountServiceWith } from "../../../../../common/spec"; @@ -25,7 +26,8 @@ describe("LocalGeneratorHistoryService", () => { encryptService.encryptString.mockImplementation((p) => Promise.resolve(p as unknown as EncString), ); - encryptService.decryptString.mockImplementation((c) => Promise.resolve(c.encryptedString)); + // in the test environment `c.encryptedString` always has a value + encryptService.decryptString.mockImplementation((c) => Promise.resolve(c.encryptedString!)); keyService.getUserKey.mockImplementation(() => Promise.resolve(userKey)); keyService.userKey$.mockImplementation(() => of(true as unknown as UserKey)); }); @@ -50,35 +52,35 @@ describe("LocalGeneratorHistoryService", () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "example", "password"); + await history.track(SomeUser, "example", Type.password); await awaitAsync(); const [result] = await firstValueFrom(history.credentials$(SomeUser)); - expect(result).toMatchObject({ credential: "example", category: "password" }); + expect(result).toMatchObject({ credential: "example", category: Type.password }); }); it("stores a passphrase", async () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "example", "passphrase"); + await history.track(SomeUser, "example", Type.password); await awaitAsync(); const [result] = await firstValueFrom(history.credentials$(SomeUser)); - expect(result).toMatchObject({ credential: "example", category: "passphrase" }); + expect(result).toMatchObject({ credential: "example", category: Type.password }); }); it("stores a specific date when one is provided", async () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "example", "password", new Date(100)); + await history.track(SomeUser, "example", Type.password, new Date(100)); await awaitAsync(); const [result] = await firstValueFrom(history.credentials$(SomeUser)); expect(result).toEqual({ credential: "example", - category: "password", + category: Type.password, generationDate: new Date(100), }); }); @@ -87,13 +89,13 @@ describe("LocalGeneratorHistoryService", () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "example", "password"); - await history.track(SomeUser, "example", "password"); - await history.track(SomeUser, "example", "passphrase"); + await history.track(SomeUser, "example", Type.password); + await history.track(SomeUser, "example", Type.password); + await history.track(SomeUser, "example", Type.password); await awaitAsync(); const [firstResult, secondResult] = await firstValueFrom(history.credentials$(SomeUser)); - expect(firstResult).toMatchObject({ credential: "example", category: "password" }); + expect(firstResult).toMatchObject({ credential: "example", category: Type.password }); expect(secondResult).toBeUndefined(); }); @@ -101,13 +103,13 @@ describe("LocalGeneratorHistoryService", () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "secondResult", "password"); - await history.track(SomeUser, "firstResult", "password"); + await history.track(SomeUser, "secondResult", Type.password); + await history.track(SomeUser, "firstResult", Type.password); await awaitAsync(); const [firstResult, secondResult] = await firstValueFrom(history.credentials$(SomeUser)); - expect(firstResult).toMatchObject({ credential: "firstResult", category: "password" }); - expect(secondResult).toMatchObject({ credential: "secondResult", category: "password" }); + expect(firstResult).toMatchObject({ credential: "firstResult", category: Type.password }); + expect(secondResult).toMatchObject({ credential: "secondResult", category: Type.password }); }); it("removes history items exceeding maxTotal configuration", async () => { @@ -116,12 +118,12 @@ describe("LocalGeneratorHistoryService", () => { maxTotal: 1, }); - await history.track(SomeUser, "removed result", "password"); - await history.track(SomeUser, "example", "password"); + await history.track(SomeUser, "removed result", Type.password); + await history.track(SomeUser, "example", Type.password); await awaitAsync(); const [firstResult, secondResult] = await firstValueFrom(history.credentials$(SomeUser)); - expect(firstResult).toMatchObject({ credential: "example", category: "password" }); + expect(firstResult).toMatchObject({ credential: "example", category: Type.password }); expect(secondResult).toBeUndefined(); }); @@ -131,8 +133,8 @@ describe("LocalGeneratorHistoryService", () => { maxTotal: 1, }); - await history.track(SomeUser, "some user example", "password"); - await history.track(AnotherUser, "another user example", "password"); + await history.track(SomeUser, "some user example", Type.password); + await history.track(AnotherUser, "another user example", Type.password); await awaitAsync(); const [someFirstResult, someSecondResult] = await firstValueFrom( history.credentials$(SomeUser), @@ -143,12 +145,12 @@ describe("LocalGeneratorHistoryService", () => { expect(someFirstResult).toMatchObject({ credential: "some user example", - category: "password", + category: Type.password, }); expect(someSecondResult).toBeUndefined(); expect(anotherFirstResult).toMatchObject({ credential: "another user example", - category: "password", + category: Type.password, }); expect(anotherSecondResult).toBeUndefined(); }); @@ -167,7 +169,7 @@ describe("LocalGeneratorHistoryService", () => { it("returns null when the credential wasn't found", async () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "example", "password"); + await history.track(SomeUser, "example", Type.password); const result = await history.take(SomeUser, "not found"); @@ -177,20 +179,20 @@ describe("LocalGeneratorHistoryService", () => { it("returns a matching credential", async () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "example", "password"); + await history.track(SomeUser, "example", Type.password); const result = await history.take(SomeUser, "example"); expect(result).toMatchObject({ credential: "example", - category: "password", + category: Type.password, }); }); it("removes a matching credential", async () => { const stateProvider = new FakeStateProvider(mockAccountServiceWith(SomeUser)); const history = new LocalGeneratorHistoryService(encryptService, keyService, stateProvider); - await history.track(SomeUser, "example", "password"); + await history.track(SomeUser, "example", Type.password); await history.take(SomeUser, "example"); await awaitAsync(); diff --git a/libs/tools/generator/extensions/history/src/local-generator-history.service.ts b/libs/tools/generator/extensions/history/src/local-generator-history.service.ts index cbfa55a184f..673319c1bfa 100644 --- a/libs/tools/generator/extensions/history/src/local-generator-history.service.ts +++ b/libs/tools/generator/extensions/history/src/local-generator-history.service.ts @@ -9,7 +9,7 @@ import { BufferedState } from "@bitwarden/common/tools/state/buffered-state"; import { PaddedDataPacker } from "@bitwarden/common/tools/state/padded-data-packer"; import { SecretState } from "@bitwarden/common/tools/state/secret-state"; import { UserId } from "@bitwarden/common/types/guid"; -import { CredentialAlgorithm } from "@bitwarden/generator-core"; +import { CredentialType } from "@bitwarden/generator-core"; import { KeyService } from "@bitwarden/key-management"; import { GeneratedCredential } from "./generated-credential"; @@ -36,12 +36,7 @@ export class LocalGeneratorHistoryService extends GeneratorHistoryService { private _credentialStates = new Map>(); /** {@link GeneratorHistoryService.track} */ - track = async ( - userId: UserId, - credential: string, - category: CredentialAlgorithm, - date?: Date, - ) => { + track = async (userId: UserId, credential: string, category: CredentialType, date?: Date) => { const state = this.getCredentialState(userId); let result: GeneratedCredential = null; diff --git a/libs/tools/generator/core/src/data/forwarders.ts b/libs/tools/generator/extensions/legacy/src/forwarders.ts similarity index 73% rename from libs/tools/generator/core/src/data/forwarders.ts rename to libs/tools/generator/extensions/legacy/src/forwarders.ts index e833fbf41d3..cb926ac3f66 100644 --- a/libs/tools/generator/core/src/data/forwarders.ts +++ b/libs/tools/generator/extensions/legacy/src/forwarders.ts @@ -1,4 +1,18 @@ -import { ForwarderMetadata } from "../types"; +import { IntegrationId } from "@bitwarden/common/tools/integration"; + +export type ForwarderId = IntegrationId; + +/** Metadata format for email forwarding services. */ +export type ForwarderMetadata = { + /** The unique identifier for the forwarder. */ + id: ForwarderId; + + /** The name of the service the forwarder queries. */ + name: string; + + /** Whether the forwarder is valid for self-hosted instances of Bitwarden. */ + validForSelfHosted: boolean; +}; /** Metadata about an email forwarding service. * @remarks This is used to populate the forwarder selection list diff --git a/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts b/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts index d932d013199..f575cd3b619 100644 --- a/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts +++ b/libs/tools/generator/extensions/legacy/src/legacy-password-generation.service.spec.ts @@ -1,13 +1,12 @@ import { mock } from "jest-mock-extended"; import { of } from "rxjs"; -import { IntegrationId } from "@bitwarden/common/tools/integration"; +import { VendorId } from "@bitwarden/common/tools/extension"; import { UserId } from "@bitwarden/common/types/guid"; import { GeneratorService, DefaultPassphraseGenerationOptions, DefaultPasswordGenerationOptions, - Policies, PassphraseGenerationOptions, PassphraseGeneratorPolicy, PasswordGenerationOptions, @@ -38,12 +37,17 @@ const PasswordGeneratorOptionsEvaluator = policies.PasswordGeneratorOptionsEvalu function createPassphraseGenerator( options: PassphraseGenerationOptions = {}, - policy: PassphraseGeneratorPolicy = Policies.Passphrase.disabledValue, + policy?: PassphraseGeneratorPolicy, ) { let savedOptions = options; const generator = mock>({ evaluator$(id: UserId) { - const evaluator = new PassphraseGeneratorOptionsEvaluator(policy); + const active = policy ?? { + minNumberWords: 0, + capitalize: false, + includeNumber: false, + }; + const evaluator = new PassphraseGeneratorOptionsEvaluator(active); return of(evaluator); }, options$(id: UserId) { @@ -63,12 +67,21 @@ function createPassphraseGenerator( function createPasswordGenerator( options: PasswordGenerationOptions = {}, - policy: PasswordGeneratorPolicy = Policies.Password.disabledValue, + policy?: PasswordGeneratorPolicy, ) { let savedOptions = options; const generator = mock>({ evaluator$(id: UserId) { - const evaluator = new PasswordGeneratorOptionsEvaluator(policy); + const active = policy ?? { + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, + }; + const evaluator = new PasswordGeneratorOptionsEvaluator(active); return of(evaluator); }, options$(id: UserId) { @@ -118,7 +131,13 @@ describe("LegacyPasswordGenerationService", () => { describe("generatePassword", () => { it("invokes the inner password generator to generate passwords", async () => { const innerPassword = createPasswordGenerator(); - const generator = new LegacyPasswordGenerationService(null, null, innerPassword, null, null); + const generator = new LegacyPasswordGenerationService( + null!, + null!, + innerPassword, + null!, + null!, + ); const options = { type: "password" } as PasswordGeneratorOptions; await generator.generatePassword(options); @@ -129,11 +148,11 @@ describe("LegacyPasswordGenerationService", () => { it("invokes the inner passphrase generator to generate passphrases", async () => { const innerPassphrase = createPassphraseGenerator(); const generator = new LegacyPasswordGenerationService( - null, - null, - null, + null!, + null!, + null!, innerPassphrase, - null, + null!, ); const options = { type: "passphrase" } as PasswordGeneratorOptions; @@ -147,11 +166,11 @@ describe("LegacyPasswordGenerationService", () => { it("invokes the inner passphrase generator", async () => { const innerPassphrase = createPassphraseGenerator(); const generator = new LegacyPasswordGenerationService( - null, - null, - null, + null!, + null!, + null!, innerPassphrase, - null, + null!, ); const options = {} as PasswordGeneratorOptions; @@ -185,7 +204,7 @@ describe("LegacyPasswordGenerationService", () => { const navigation = createNavigationGenerator({ type: "passphrase", username: "word", - forwarder: "simplelogin" as IntegrationId, + forwarder: "simplelogin" as VendorId, }); const accountService = mockAccountServiceWith(SomeUser); const generator = new LegacyPasswordGenerationService( @@ -193,7 +212,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const [result] = await generator.getOptions(); @@ -220,16 +239,16 @@ describe("LegacyPasswordGenerationService", () => { }); it("sets default options when an inner service lacks a value", async () => { - const innerPassword = createPasswordGenerator(null); - const innerPassphrase = createPassphraseGenerator(null); - const navigation = createNavigationGenerator(null); + const innerPassword = createPasswordGenerator(null!); + const innerPassphrase = createPassphraseGenerator(null!); + const navigation = createNavigationGenerator(null!); const accountService = mockAccountServiceWith(SomeUser); const generator = new LegacyPasswordGenerationService( accountService, navigation, innerPassword, innerPassphrase, - null, + null!, ); const [result] = await generator.getOptions(); @@ -277,7 +296,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const [, policy] = await generator.getOptions(); @@ -323,7 +342,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const [result] = await generator.enforcePasswordGeneratorPoliciesOnOptions(options); @@ -363,7 +382,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const [result] = await generator.enforcePasswordGeneratorPoliciesOnOptions(options); @@ -409,7 +428,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const [, policy] = await generator.enforcePasswordGeneratorPoliciesOnOptions({}); @@ -441,7 +460,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const options = { type: "password" as const, @@ -474,7 +493,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const options = { type: "passphrase" as const, @@ -496,7 +515,7 @@ describe("LegacyPasswordGenerationService", () => { const navigation = createNavigationGenerator({ type: "password", username: "forwarded", - forwarder: "firefoxrelay" as IntegrationId, + forwarder: "firefoxrelay" as VendorId, }); const accountService = mockAccountServiceWith(SomeUser); const generator = new LegacyPasswordGenerationService( @@ -504,7 +523,7 @@ describe("LegacyPasswordGenerationService", () => { navigation, innerPassword, innerPassphrase, - null, + null!, ); const options = { type: "passphrase" as const, @@ -533,9 +552,9 @@ describe("LegacyPasswordGenerationService", () => { const accountService = mockAccountServiceWith(SomeUser); const generator = new LegacyPasswordGenerationService( accountService, - null, - null, - null, + null!, + null!, + null!, history, ); @@ -552,9 +571,9 @@ describe("LegacyPasswordGenerationService", () => { const accountService = mockAccountServiceWith(SomeUser); const generator = new LegacyPasswordGenerationService( accountService, - null, - null, - null, + null!, + null!, + null!, history, ); diff --git a/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.spec.ts b/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.spec.ts index 08ffce2eba5..5a4dce4f4a5 100644 --- a/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.spec.ts +++ b/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.spec.ts @@ -1,6 +1,15 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { mock } from "jest-mock-extended"; import { of } from "rxjs"; +import { AddyIo } from "@bitwarden/common/tools/extension/vendor/addyio"; +import { DuckDuckGo } from "@bitwarden/common/tools/extension/vendor/duckduckgo"; +import { Fastmail } from "@bitwarden/common/tools/extension/vendor/fastmail"; +import { ForwardEmail } from "@bitwarden/common/tools/extension/vendor/forwardemail"; +import { Mozilla } from "@bitwarden/common/tools/extension/vendor/mozilla"; +import { SimpleLogin } from "@bitwarden/common/tools/extension/vendor/simplelogin"; +import { IntegrationId } from "@bitwarden/common/tools/integration"; import { UserId } from "@bitwarden/common/types/guid"; import { ApiOptions, @@ -13,13 +22,6 @@ import { DefaultCatchallOptions, DefaultEffUsernameOptions, EffUsernameGenerationOptions, - DefaultAddyIoOptions, - DefaultDuckDuckGoOptions, - DefaultFastmailOptions, - DefaultFirefoxRelayOptions, - DefaultForwardEmailOptions, - DefaultSimpleLoginOptions, - Forwarders, DefaultSubaddressOptions, SubaddressGenerationOptions, policies, @@ -169,7 +171,7 @@ describe("LegacyUsernameGenerationService", () => { // set up an arbitrary forwarder for the username test; all forwarders tested in their own tests const options = { type: "forwarded", - forwardedService: Forwarders.AddyIo.id, + forwardedService: AddyIo.id, } as UsernameGeneratorOptions; const addyIo = createGenerator(null, null); addyIo.generate.mockResolvedValue("addyio@example.com"); @@ -249,7 +251,7 @@ describe("LegacyUsernameGenerationService", () => { describe("generateForwarded", () => { it("should generate a AddyIo username", async () => { const options = { - forwardedService: Forwarders.AddyIo.id, + forwardedService: AddyIo.id, forwardedAnonAddyApiToken: "token", forwardedAnonAddyBaseUrl: "https://example.com", forwardedAnonAddyDomain: "example.com", @@ -284,7 +286,7 @@ describe("LegacyUsernameGenerationService", () => { it("should generate a DuckDuckGo username", async () => { const options = { - forwardedService: Forwarders.DuckDuckGo.id, + forwardedService: DuckDuckGo.id, forwardedDuckDuckGoToken: "token", website: "example.com", } as UsernameGeneratorOptions; @@ -315,7 +317,7 @@ describe("LegacyUsernameGenerationService", () => { it("should generate a Fastmail username", async () => { const options = { - forwardedService: Forwarders.Fastmail.id, + forwardedService: Fastmail.id, forwardedFastmailApiToken: "token", website: "example.com", } as UsernameGeneratorOptions; @@ -346,7 +348,7 @@ describe("LegacyUsernameGenerationService", () => { it("should generate a FirefoxRelay username", async () => { const options = { - forwardedService: Forwarders.FirefoxRelay.id, + forwardedService: Mozilla.id, forwardedFirefoxApiToken: "token", website: "example.com", } as UsernameGeneratorOptions; @@ -377,7 +379,7 @@ describe("LegacyUsernameGenerationService", () => { it("should generate a ForwardEmail username", async () => { const options = { - forwardedService: Forwarders.ForwardEmail.id, + forwardedService: ForwardEmail.id, forwardedForwardEmailApiToken: "token", forwardedForwardEmailDomain: "example.com", website: "example.com", @@ -410,7 +412,7 @@ describe("LegacyUsernameGenerationService", () => { it("should generate a SimpleLogin username", async () => { const options = { - forwardedService: Forwarders.SimpleLogin.id, + forwardedService: SimpleLogin.id, forwardedSimpleLoginApiKey: "token", forwardedSimpleLoginBaseUrl: "https://example.com", website: "example.com", @@ -449,7 +451,7 @@ describe("LegacyUsernameGenerationService", () => { const navigation = createNavigationGenerator({ type: "username", username: "catchall", - forwarder: Forwarders.AddyIo.id, + forwarder: AddyIo.id, }); const catchall = createGenerator( @@ -557,7 +559,7 @@ describe("LegacyUsernameGenerationService", () => { subaddressEmail: "foo@example.com", catchallType: "random", catchallDomain: "example.com", - forwardedService: Forwarders.AddyIo.id, + forwardedService: AddyIo.id, forwardedAnonAddyApiToken: "addyIoToken", forwardedAnonAddyDomain: "addyio.example.com", forwardedAnonAddyBaseUrl: "https://addyio.api.example.com", @@ -583,21 +585,36 @@ describe("LegacyUsernameGenerationService", () => { null, DefaultSubaddressOptions, ); - const addyIo = createGenerator( - null, - DefaultAddyIoOptions, - ); - const duckDuckGo = createGenerator(null, DefaultDuckDuckGoOptions); - const fastmail = createGenerator( - null, - DefaultFastmailOptions, - ); - const firefoxRelay = createGenerator(null, DefaultFirefoxRelayOptions); - const forwardEmail = createGenerator( - null, - DefaultForwardEmailOptions, - ); - const simpleLogin = createGenerator(null, DefaultSimpleLoginOptions); + const addyIo = createGenerator(null, { + website: null, + baseUrl: "https://app.addy.io", + token: "", + domain: "", + }); + const duckDuckGo = createGenerator(null, { + website: null, + token: "", + }); + const fastmail = createGenerator(null, { + website: "", + domain: "", + prefix: "", + token: "", + }); + const firefoxRelay = createGenerator(null, { + website: null, + token: "", + }); + const forwardEmail = createGenerator(null, { + website: null, + token: "", + domain: "", + }); + const simpleLogin = createGenerator(null, { + website: null, + baseUrl: "https://app.simplelogin.io", + token: "", + }); const generator = new LegacyUsernameGenerationService( account, @@ -624,16 +641,16 @@ describe("LegacyUsernameGenerationService", () => { subaddressType: DefaultSubaddressOptions.subaddressType, subaddressEmail: DefaultSubaddressOptions.subaddressEmail, forwardedService: DefaultGeneratorNavigation.forwarder, - forwardedAnonAddyApiToken: DefaultAddyIoOptions.token, - forwardedAnonAddyDomain: DefaultAddyIoOptions.domain, - forwardedAnonAddyBaseUrl: DefaultAddyIoOptions.baseUrl, - forwardedDuckDuckGoToken: DefaultDuckDuckGoOptions.token, - forwardedFastmailApiToken: DefaultFastmailOptions.token, - forwardedFirefoxApiToken: DefaultFirefoxRelayOptions.token, - forwardedForwardEmailApiToken: DefaultForwardEmailOptions.token, - forwardedForwardEmailDomain: DefaultForwardEmailOptions.domain, - forwardedSimpleLoginApiKey: DefaultSimpleLoginOptions.token, - forwardedSimpleLoginBaseUrl: DefaultSimpleLoginOptions.baseUrl, + forwardedAnonAddyApiToken: "", + forwardedAnonAddyDomain: "", + forwardedAnonAddyBaseUrl: "https://app.addy.io", + forwardedDuckDuckGoToken: "", + forwardedFastmailApiToken: "", + forwardedFirefoxApiToken: "", + forwardedForwardEmailApiToken: "", + forwardedForwardEmailDomain: "", + forwardedSimpleLoginApiKey: "", + forwardedSimpleLoginBaseUrl: "https://app.simplelogin.io", }); }); }); @@ -678,7 +695,7 @@ describe("LegacyUsernameGenerationService", () => { subaddressEmail: "foo@example.com", catchallType: "random", catchallDomain: "example.com", - forwardedService: Forwarders.AddyIo.id, + forwardedService: AddyIo.id as IntegrationId, forwardedAnonAddyApiToken: "addyIoToken", forwardedAnonAddyDomain: "addyio.example.com", forwardedAnonAddyBaseUrl: "https://addyio.api.example.com", @@ -697,7 +714,7 @@ describe("LegacyUsernameGenerationService", () => { expect(navigation.saveOptions).toHaveBeenCalledWith(SomeUser, { type: "password", username: "catchall", - forwarder: Forwarders.AddyIo.id, + forwarder: AddyIo.id, }); expect(catchall.saveOptions).toHaveBeenCalledWith(SomeUser, { diff --git a/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.ts b/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.ts index c6e9118535f..48da3404cd6 100644 --- a/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.ts +++ b/libs/tools/generator/extensions/legacy/src/legacy-username-generation.service.ts @@ -3,6 +3,7 @@ import { zip, firstValueFrom, map, concatMap, combineLatest } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { Vendor } from "@bitwarden/common/tools/extension/vendor/data"; import { IntegrationRequest } from "@bitwarden/common/tools/integration/rpc"; import { UserId } from "@bitwarden/common/types/guid"; import { @@ -14,13 +15,13 @@ import { GeneratorService, CatchallGenerationOptions, EffUsernameGenerationOptions, - Forwarders, SubaddressGenerationOptions, UsernameGeneratorType, ForwarderId, } from "@bitwarden/generator-core"; import { GeneratorNavigationService, GeneratorNavigation } from "@bitwarden/generator-navigation"; +import { Forwarders } from "./forwarders"; import { UsernameGeneratorOptions } from "./username-generation-options"; import { UsernameGenerationServiceAbstraction } from "./username-generation.service.abstraction"; @@ -89,12 +90,14 @@ export class LegacyUsernameGenerationService implements UsernameGenerationServic const stored = this.toStoredOptions(options); switch (options.forwardedService) { case Forwarders.AddyIo.id: + case Vendor.addyio: return this.addyIo.generate(stored.forwarders.addyIo); case Forwarders.DuckDuckGo.id: return this.duckDuckGo.generate(stored.forwarders.duckDuckGo); case Forwarders.Fastmail.id: return this.fastmail.generate(stored.forwarders.fastmail); case Forwarders.FirefoxRelay.id: + case Vendor.mozilla: return this.firefoxRelay.generate(stored.forwarders.firefoxRelay); case Forwarders.ForwardEmail.id: return this.forwardEmail.generate(stored.forwarders.forwardEmail); @@ -232,22 +235,24 @@ export class LegacyUsernameGenerationService implements UsernameGenerationServic options: MappedOptions, ) { switch (forwarder) { - case "anonaddy": + case Forwarders.AddyIo.id: + case Vendor.addyio: await this.addyIo.saveOptions(account, options.forwarders.addyIo); return true; - case "duckduckgo": + case Forwarders.DuckDuckGo.id: await this.duckDuckGo.saveOptions(account, options.forwarders.duckDuckGo); return true; - case "fastmail": + case Forwarders.Fastmail.id: await this.fastmail.saveOptions(account, options.forwarders.fastmail); return true; - case "firefoxrelay": + case Forwarders.FirefoxRelay.id: + case Vendor.mozilla: await this.firefoxRelay.saveOptions(account, options.forwarders.firefoxRelay); return true; - case "forwardemail": + case Forwarders.ForwardEmail.id: await this.forwardEmail.saveOptions(account, options.forwarders.forwardEmail); return true; - case "simplelogin": + case Forwarders.SimpleLogin.id: await this.simpleLogin.saveOptions(account, options.forwarders.simpleLogin); return true; default: diff --git a/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.spec.ts b/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.spec.ts index 218121a3a75..82b9e29e91a 100644 --- a/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.spec.ts +++ b/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.spec.ts @@ -24,7 +24,7 @@ describe("GeneratorNavigationEvaluator", () => { describe("applyPolicy", () => { it("returns the input options when a policy is not in effect", () => { - const evaluator = new GeneratorNavigationEvaluator(null); + const evaluator = new GeneratorNavigationEvaluator(null!); const options = { type: "password" as const }; const result = evaluator.applyPolicy(options); @@ -54,7 +54,7 @@ describe("GeneratorNavigationEvaluator", () => { }); it("defaults options to the default generator navigation type when a policy is not in effect", () => { - const evaluator = new GeneratorNavigationEvaluator(null); + const evaluator = new GeneratorNavigationEvaluator(null!); const result = evaluator.sanitize({}); diff --git a/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts b/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts index 2f891cdea03..5446c1f26ad 100644 --- a/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts +++ b/libs/tools/generator/extensions/navigation/src/generator-navigation-evaluator.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { PasswordAlgorithms, PolicyEvaluator } from "@bitwarden/generator-core"; +import { AlgorithmsByType, PolicyEvaluator, Type } from "@bitwarden/generator-core"; import { DefaultGeneratorNavigation } from "./default-generator-navigation"; import { GeneratorNavigation } from "./generator-navigation"; @@ -19,7 +19,7 @@ export class GeneratorNavigationEvaluator /** {@link PolicyEvaluator.policyInEffect} */ get policyInEffect(): boolean { - return PasswordAlgorithms.includes(this.policy?.overridePasswordType); + return AlgorithmsByType[Type.password].includes(this.policy?.overridePasswordType); } /** Apply policy to the input options. diff --git a/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.ts b/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.ts index cba1f91dad3..fc920a66e0d 100644 --- a/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.ts +++ b/libs/tools/generator/extensions/navigation/src/generator-navigation-policy.ts @@ -4,14 +4,14 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; // FIXME: use index.ts imports once policy abstractions and models // implement ADR-0002 import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { PasswordType } from "@bitwarden/generator-core"; +import { PasswordAlgorithm } from "@bitwarden/generator-core"; /** Policy settings affecting password generator navigation */ export type GeneratorNavigationPolicy = { /** The type of generator that should be shown by default when opening * the password generator. */ - overridePasswordType?: PasswordType; + overridePasswordType?: PasswordAlgorithm; }; /** Reduces a policy into an accumulator by preferring the password generator diff --git a/libs/tools/generator/extensions/navigation/src/generator-navigation.ts b/libs/tools/generator/extensions/navigation/src/generator-navigation.ts index 5a35e57d7b4..08e5025244d 100644 --- a/libs/tools/generator/extensions/navigation/src/generator-navigation.ts +++ b/libs/tools/generator/extensions/navigation/src/generator-navigation.ts @@ -1,4 +1,5 @@ -import { GeneratorType, ForwarderId, UsernameGeneratorType } from "@bitwarden/generator-core"; +import { VendorId } from "@bitwarden/common/tools/extension"; +import { UsernameGeneratorType, CredentialAlgorithm } from "@bitwarden/generator-core"; /** Stores credential generator UI state. */ export type GeneratorNavigation = { @@ -6,11 +7,11 @@ export type GeneratorNavigation = { * @remarks The legacy generator only supports "password" and "passphrase". * The componentized generator supports all values. */ - type?: GeneratorType; + type?: CredentialAlgorithm; /** When `type === "username"`, this stores the username algorithm. */ username?: UsernameGeneratorType; /** When `username === "forwarded"`, this stores the forwarder implementation. */ - forwarder?: ForwarderId | ""; + forwarder?: VendorId | ""; }; diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts index 8d24fc4acee..30775aa8a83 100644 --- a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts @@ -28,7 +28,7 @@ import { ToastService, TypographyModule, } from "@bitwarden/components"; -import { CredentialGeneratorService, GenerateRequest, Generators } from "@bitwarden/generator-core"; +import { CredentialGeneratorService, GenerateRequest, Type } from "@bitwarden/generator-core"; import { SendFormConfig } from "../../abstractions/send-form-config.service"; import { SendFormContainer } from "../../send-form-container"; @@ -122,12 +122,12 @@ export class SendOptionsComponent implements OnInit { } generatePassword = async () => { - const on$ = new BehaviorSubject({ source: "send" }); + const on$ = new BehaviorSubject({ source: "send", type: Type.password }); const account$ = this.accountService.activeAccount$.pipe( pin({ name: () => "send-options.component", distinct: (p, c) => p.id === c.id }), ); const generatedCredential = await firstValueFrom( - this.generatorService.generate$(Generators.password, { on$, account$ }), + this.generatorService.generate$({ on$, account$ }), ); this.sendOptionsForm.patchValue({ From b2d38249ba33416faa8c88ed361ae18cc330ff86 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Tue, 27 May 2025 16:14:36 +0200 Subject: [PATCH 162/245] Remove class user-select: The copy button contains all the information and is the preferred method (#14944) Co-authored-by: Daniel James Smith --- .../popup/settings/about-dialog/about-dialog.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html index 40dad4cde4b..af68959fe5d 100644 --- a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html +++ b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html @@ -6,7 +6,7 @@

© Bitwarden Inc. 2015-{{ year }}

-
+

{{ "version" | i18n }}: {{ version$ | async }}

SDK: {{ sdkVersion$ | async }}

From d1aa8422e0319ac3b33dd5d9365162ecfe3fa4e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Tue, 27 May 2025 11:09:50 -0400 Subject: [PATCH 163/245] align sdk generator types (#14967) --- .../core/src/metadata/password/sdk-eff-word-list.spec.ts | 3 ++- .../core/src/metadata/password/sdk-eff-word-list.ts | 9 +++------ .../src/metadata/password/sdk-random-password.spec.ts | 3 ++- .../core/src/metadata/password/sdk-random-password.ts | 9 +++------ 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.spec.ts b/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.spec.ts index 03f1275f73c..29378b4cdd3 100644 --- a/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.spec.ts +++ b/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.spec.ts @@ -5,7 +5,8 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { SdkPasswordRandomizer } from "../../engine"; import { PassphrasePolicyConstraints } from "../../policies"; -import { PassphraseGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { PassphraseGenerationOptions } from "../../types"; import { Profile } from "../data"; import { CoreProfileMetadata } from "../profile-metadata"; import { isCoreProfile } from "../util"; diff --git a/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.ts b/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.ts index 802ef0ef068..8892016b609 100644 --- a/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.ts +++ b/libs/tools/generator/core/src/metadata/password/sdk-eff-word-list.ts @@ -6,17 +6,14 @@ import { BitwardenClient } from "@bitwarden/sdk-internal"; import { SdkPasswordRandomizer } from "../../engine"; import { passphraseLeastPrivilege, PassphrasePolicyConstraints } from "../../policies"; -import { - CredentialGenerator, - GeneratorDependencyProvider, - PassphraseGenerationOptions, -} from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CredentialGenerator, PassphraseGenerationOptions } from "../../types"; import { Algorithm, Profile, Type } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; const sdkPassphrase: GeneratorMetadata = { id: Algorithm.sdkPassphrase, - category: Type.password, + type: Type.password, weight: 130, i18nKeys: { name: "passphrase", diff --git a/libs/tools/generator/core/src/metadata/password/sdk-random-password.spec.ts b/libs/tools/generator/core/src/metadata/password/sdk-random-password.spec.ts index cec4704789f..1e9cf6dbd87 100644 --- a/libs/tools/generator/core/src/metadata/password/sdk-random-password.spec.ts +++ b/libs/tools/generator/core/src/metadata/password/sdk-random-password.spec.ts @@ -5,7 +5,8 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { SdkPasswordRandomizer } from "../../engine"; import { DynamicPasswordPolicyConstraints } from "../../policies"; -import { PasswordGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { PasswordGenerationOptions } from "../../types"; import { Profile } from "../data"; import { CoreProfileMetadata } from "../profile-metadata"; import { isCoreProfile } from "../util"; diff --git a/libs/tools/generator/core/src/metadata/password/sdk-random-password.ts b/libs/tools/generator/core/src/metadata/password/sdk-random-password.ts index d4bb6263b1e..d6544fa115e 100644 --- a/libs/tools/generator/core/src/metadata/password/sdk-random-password.ts +++ b/libs/tools/generator/core/src/metadata/password/sdk-random-password.ts @@ -6,17 +6,14 @@ import { BitwardenClient } from "@bitwarden/sdk-internal"; import { SdkPasswordRandomizer } from "../../engine"; import { DynamicPasswordPolicyConstraints, passwordLeastPrivilege } from "../../policies"; -import { - CredentialGenerator, - GeneratorDependencyProvider, - PasswordGeneratorSettings, -} from "../../types"; +import { GeneratorDependencyProvider } from "../../providers"; +import { CredentialGenerator, PasswordGeneratorSettings } from "../../types"; import { Algorithm, Profile, Type } from "../data"; import { GeneratorMetadata } from "../generator-metadata"; const sdkPassword: GeneratorMetadata = deepFreeze({ id: Algorithm.sdkPassword, - category: Type.password, + type: Type.password, weight: 120, i18nKeys: { name: "password", From abb01d9038819f39e8937dec6e995b9db62f7e44 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Tue, 27 May 2025 11:14:10 -0400 Subject: [PATCH 164/245] ensure loginview properties have correct defaults when usding SDK decryption (#14948) --- libs/common/src/vault/models/view/login.view.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/vault/models/view/login.view.ts b/libs/common/src/vault/models/view/login.view.ts index 41568f643d5..6bdc23f42b1 100644 --- a/libs/common/src/vault/models/view/login.view.ts +++ b/libs/common/src/vault/models/view/login.view.ts @@ -118,7 +118,7 @@ export class LoginView extends ItemView { const passwordRevisionDate = obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); - const uris = obj.uris?.map((uri) => LoginUriView.fromSdkLoginUriView(uri)); + const uris = obj.uris?.map((uri) => LoginUriView.fromSdkLoginUriView(uri)) || []; return Object.assign(new LoginView(), obj, { passwordRevisionDate, From 0f9f6a1c5ddfccc2632011066375cb1b847a6a47 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 18:58:56 +0200 Subject: [PATCH 165/245] [deps] Tools: Update papaparse to v5.5.3 (#14966) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- apps/cli/package.json | 2 +- package-lock.json | 18 +++++++++--------- package.json | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 4c8d2918ce1..eeebf4dad6f 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -86,7 +86,7 @@ "node-fetch": "2.6.12", "node-forge": "1.3.1", "open": "8.4.2", - "papaparse": "5.5.2", + "papaparse": "5.5.3", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", "tldts": "7.0.1", diff --git a/package-lock.json b/package-lock.json index ff21acbc20b..f0dd03b5e74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,7 +59,7 @@ "node-forge": "1.3.1", "oidc-client-ts": "2.4.1", "open": "8.4.2", - "papaparse": "5.5.2", + "papaparse": "5.5.3", "patch-package": "8.0.0", "proper-lockfile": "4.1.2", "qrcode-parser": "2.1.3", @@ -111,7 +111,7 @@ "@types/node": "22.15.3", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", - "@types/papaparse": "5.3.15", + "@types/papaparse": "5.3.16", "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", @@ -219,7 +219,7 @@ "node-fetch": "2.6.12", "node-forge": "1.3.1", "open": "8.4.2", - "papaparse": "5.5.2", + "papaparse": "5.5.3", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", "tldts": "7.0.1", @@ -11939,9 +11939,9 @@ } }, "node_modules/@types/papaparse": { - "version": "5.3.15", - "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.15.tgz", - "integrity": "sha512-JHe6vF6x/8Z85nCX4yFdDslN11d+1pr12E526X8WAfhadOeaOTx5AuIkvDKIBopfvlzpzkdMx4YyvSKCM9oqtw==", + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.16.tgz", + "integrity": "sha512-T3VuKMC2H0lgsjI9buTB3uuKj3EMD2eap1MOuEQuBQ44EnDx/IkGhU6EwiTf9zG3za4SKlmwKAImdDKdNnCsXg==", "dev": true, "license": "MIT", "dependencies": { @@ -30738,9 +30738,9 @@ "license": "(MIT AND Zlib)" }, "node_modules/papaparse": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.2.tgz", - "integrity": "sha512-PZXg8UuAc4PcVwLosEEDYjPyfWnTEhOrUfdv+3Bx+NuAb+5NhDmXzg5fHWmdCh1mP5p7JAZfFr3IMQfcntNAdA==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz", + "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==", "license": "MIT" }, "node_modules/param-case": { diff --git a/package.json b/package.json index f63db41e1f3..fe7f69ff40d 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "@types/node": "22.15.3", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", - "@types/papaparse": "5.3.15", + "@types/papaparse": "5.3.16", "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", @@ -194,7 +194,7 @@ "node-forge": "1.3.1", "oidc-client-ts": "2.4.1", "open": "8.4.2", - "papaparse": "5.5.2", + "papaparse": "5.5.3", "patch-package": "8.0.0", "proper-lockfile": "4.1.2", "qrcode-parser": "2.1.3", From 5f169af08e69c4b957df3e5c9aa6baef56cb4616 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 27 May 2025 10:03:29 -0700 Subject: [PATCH 166/245] [PM-21122] - Hide orgs in product switcher for single org policy users (#14803) * don't display orgs in account switcher for single org policy users * add comment * add test case * fix storybook * fix storybook again * use variable name instead of comment --- .../navigation-switcher.stories.ts | 7 +++++++ .../product-switcher/product-switcher.stories.ts | 7 +++++++ .../shared/product-switcher.service.spec.ts | 16 ++++++++++++++++ .../shared/product-switcher.service.ts | 16 ++++++++++++++-- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index b1c1a0a906a..0ecec9d8944 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -5,6 +5,7 @@ import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; @@ -130,6 +131,12 @@ export default { return new I18nMockService(translations); }, }, + { + provide: PolicyService, + useValue: { + policyAppliesToUser$: () => of(false), + }, + }, ], }), applicationConfig({ diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts index 4525105e579..0b7304a3657 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts @@ -5,6 +5,7 @@ import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; @@ -126,6 +127,12 @@ export default { }); }, }, + { + provide: PolicyService, + useValue: { + policyAppliesToUser$: () => of(false), + }, + }, ], }), applicationConfig({ diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts index a1ac434d590..4abd85d7991 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts @@ -7,6 +7,7 @@ import { Observable, firstValueFrom, of } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; @@ -27,6 +28,7 @@ describe("ProductSwitcherService", () => { let accountService: FakeAccountService; let platformUtilsService: MockProxy; let activeRouteParams = convertToParamMap({ organizationId: "1234" }); + let singleOrgPolicyEnabled = false; const getLastSync = jest.fn().mockResolvedValue(new Date("2024-05-14")); const userId = Utils.newGuid() as UserId; @@ -77,6 +79,12 @@ describe("ProductSwitcherService", () => { provide: SyncService, useValue: { getLastSync }, }, + { + provide: PolicyService, + useValue: { + policyAppliesToUser$: () => of(singleOrgPolicyEnabled), + }, + }, ], }); }); @@ -184,6 +192,14 @@ describe("ProductSwitcherService", () => { expect(products.bento.find((p) => p.name === "Admin Console")).toBeDefined(); expect(products.other.find((p) => p.name === "Organizations")).toBeUndefined(); }); + + it("does not include Organizations when the user's single org policy is enabled", async () => { + singleOrgPolicyEnabled = true; + initiateService(); + const products = await firstValueFrom(service.products$); + + expect(products.other.find((p) => p.name === "Organizations")).not.toBeDefined(); + }); }); describe("Provider Portal", () => { diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts index ec0d2c2651c..53ec3b0840f 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts @@ -6,6 +6,7 @@ import { combineLatest, concatMap, filter, + firstValueFrom, map, Observable, ReplaySubject, @@ -18,10 +19,12 @@ import { canAccessOrgAdmin, OrganizationService, } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { ProviderType } from "@bitwarden/common/admin-console/enums"; +import { PolicyType, ProviderType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; @@ -104,6 +107,7 @@ export class ProductSwitcherService { private syncService: SyncService, private accountService: AccountService, private platformUtilsService: PlatformUtilsService, + private policyService: PolicyService, ) { this.pollUntilSynced(); } @@ -235,7 +239,15 @@ export class ProductSwitcherService { if (acOrg) { bento.push(products.ac); } else { - other.push(products.orgs); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getUserId), + ); + const userHasSingleOrgPolicy = await firstValueFrom( + this.policyService.policyAppliesToUser$(PolicyType.SingleOrg, activeUserId), + ); + if (!userHasSingleOrgPolicy) { + other.push(products.orgs); + } } if (providers.length > 0) { From 677a435cadddf26dbca9461d78a0950c52360c5f Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 27 May 2025 10:05:58 -0700 Subject: [PATCH 167/245] prevent showing ssh key when cipher changes in desktop view (#14913) --- apps/desktop/src/vault/app/vault/view.component.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index 68bf8d6d1a3..33dfc600265 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -9,6 +9,7 @@ import { OnDestroy, OnInit, Output, + SimpleChanges, } from "@angular/core"; import { ViewComponent as BaseViewComponent } from "@bitwarden/angular/vault/components/view.component"; @@ -130,7 +131,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); } - async ngOnChanges() { + async ngOnChanges(changes: SimpleChanges) { if (this.cipher?.decryptionFailure) { DecryptionFailureDialogComponent.open(this.dialogService, { cipherIds: [this.cipherId as CipherId], @@ -138,6 +139,12 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro return; } this.passwordReprompted = this.masterPasswordAlreadyPrompted; + + if (changes["cipherId"]) { + if (changes["cipherId"].currentValue !== changes["cipherId"].previousValue) { + this.showPrivateKey = false; + } + } } viewHistory() { From 5423ab326898c77950172b4f8cdd852a7f12d1a5 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 27 May 2025 19:13:15 +0200 Subject: [PATCH 168/245] [PM-21934] Upgrade to eslint 9 (#14754) Upgrades to Eslint v9. Since this is a major version there were breaking changes, but since we've previously migrated to flat configs in #12806 those were minimal. --- .github/renovate.json5 | 2 + .../vault-timeout/vault-timeout.service.ts | 1 - .../popup/services/browser-router.service.ts | 2 + .../view-cache/popup-router-cache.service.ts | 2 + apps/cli/src/utils.ts | 1 - .../organization-reporting-routing.module.ts | 6 +- .../change-plan-dialog.component.ts | 2 + .../organization-payment-method.component.ts | 2 + .../shared/payment-method.component.ts | 2 + apps/web/src/app/core/router.service.ts | 3 + eslint.config.mjs | 6 +- .../device-trust.service.implementation.ts | 4 + libs/eslint/fix-jsdom.ts | 10 + libs/eslint/jest.config.js | 1 + libs/eslint/test.setup.mjs | 2 - libs/eslint/tsconfig.json | 3 +- .../dashlane/dashlane-csv-importer.ts | 2 +- package-lock.json | 2758 ++++++++++++----- package.json | 9 +- 19 files changed, 1961 insertions(+), 857 deletions(-) create mode 100644 libs/eslint/fix-jsdom.ts diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 1b84ccdab01..f30bc06e4a2 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -59,6 +59,7 @@ { matchPackageNames: [ "@angular-eslint/schematics", + "@eslint/compat", "@typescript-eslint/rule-tester", "@typescript-eslint/utils", "angular-eslint", @@ -81,6 +82,7 @@ { matchPackageNames: [ "@angular-eslint/schematics", + "@eslint/compat", "@typescript-eslint/rule-tester", "@typescript-eslint/utils", "angular-eslint", diff --git a/apps/browser/src/key-management/vault-timeout/vault-timeout.service.ts b/apps/browser/src/key-management/vault-timeout/vault-timeout.service.ts index 51f90fb98a6..c0de04b02d7 100644 --- a/apps/browser/src/key-management/vault-timeout/vault-timeout.service.ts +++ b/apps/browser/src/key-management/vault-timeout/vault-timeout.service.ts @@ -17,7 +17,6 @@ export default class VaultTimeoutService extends BaseVaultTimeoutService { // setIntervals. It works by calling the native extension which sleeps for 10s, // efficiently replicating setInterval. async checkSafari() { - // eslint-disable-next-line while (true) { try { await SafariApp.sendMessageToApp("sleep"); diff --git a/apps/browser/src/platform/popup/services/browser-router.service.ts b/apps/browser/src/platform/popup/services/browser-router.service.ts index 413bde5fcad..2d449b8a0f2 100644 --- a/apps/browser/src/platform/popup/services/browser-router.service.ts +++ b/apps/browser/src/platform/popup/services/browser-router.service.ts @@ -21,6 +21,8 @@ export class BrowserRouterService { child = child.firstChild; } + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression const updateUrl = !child?.data?.doNotSaveUrl ?? true; if (updateUrl) { diff --git a/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts b/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts index aa0c0854eff..5fc508ac2a6 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts @@ -62,6 +62,8 @@ export class PopupRouterCacheService { child = child.firstChild; } + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression return !child?.data?.doNotSaveUrl ?? true; }), switchMap((event) => this.push(event.url)), diff --git a/apps/cli/src/utils.ts b/apps/cli/src/utils.ts index fadede9c71b..e321adbfd5e 100644 --- a/apps/cli/src/utils.ts +++ b/apps/cli/src/utils.ts @@ -161,7 +161,6 @@ export class CliUtils { process.stdin.setEncoding("utf8"); process.stdin.on("readable", () => { - // eslint-disable-next-line while (true) { const chunk = process.stdin.read(); if (chunk == null) { diff --git a/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts b/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts index 23751653331..4c825b26bb2 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts @@ -9,12 +9,16 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +// eslint-disable-next-line no-restricted-imports import { ExposedPasswordsReportComponent } from "../../../dirt/reports/pages/organizations/exposed-passwords-report.component"; +// eslint-disable-next-line no-restricted-imports import { InactiveTwoFactorReportComponent } from "../../../dirt/reports/pages/organizations/inactive-two-factor-report.component"; +// eslint-disable-next-line no-restricted-imports import { ReusedPasswordsReportComponent } from "../../../dirt/reports/pages/organizations/reused-passwords-report.component"; +// eslint-disable-next-line no-restricted-imports import { UnsecuredWebsitesReportComponent } from "../../../dirt/reports/pages/organizations/unsecured-websites-report.component"; +// eslint-disable-next-line no-restricted-imports import { WeakPasswordsReportComponent } from "../../../dirt/reports/pages/organizations/weak-passwords-report.component"; -/* eslint no-restricted-imports: "error" */ import { isPaidOrgGuard } from "../guards/is-paid-org.guard"; import { organizationPermissionsGuard } from "../guards/org-permissions.guard"; import { organizationRedirectGuard } from "../guards/org-redirect.guard"; diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts index 9b6694a3bbe..a6e8670d944 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts @@ -607,6 +607,8 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { return ( plan.PasswordManager.additionalStoragePricePerGb * + // TODO: Eslint upgrade. Please resolve this since the null check does nothing + // eslint-disable-next-line no-constant-binary-expression Math.abs(this.sub?.maxStorageGb ? this.sub?.maxStorageGb - 1 : 0 || 0) ); } diff --git a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts index c896ee6404c..fbd7453c712 100644 --- a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts +++ b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts @@ -148,6 +148,8 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { paymentSource, ); } + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression this.isUnpaid = this.subscriptionStatus === "unpaid" ?? false; // If the flag `launchPaymentModalAutomatically` is set to true, // we schedule a timeout (delay of 800ms) to automatically launch the payment modal. diff --git a/apps/web/src/app/billing/shared/payment-method.component.ts b/apps/web/src/app/billing/shared/payment-method.component.ts index 27c9caf7186..74793bccc01 100644 --- a/apps/web/src/app/billing/shared/payment-method.component.ts +++ b/apps/web/src/app/billing/shared/payment-method.component.ts @@ -143,6 +143,8 @@ export class PaymentMethodComponent implements OnInit, OnDestroy { [this.billing, this.sub] = await Promise.all([billingPromise, subPromise]); } + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression this.isUnpaid = this.subscription?.status === "unpaid" ?? false; this.loading = false; // If the flag `launchPaymentModalAutomatically` is set to true, diff --git a/apps/web/src/app/core/router.service.ts b/apps/web/src/app/core/router.service.ts index ff0aea47b9a..603c171e95b 100644 --- a/apps/web/src/app/core/router.service.ts +++ b/apps/web/src/app/core/router.service.ts @@ -74,6 +74,9 @@ export class RouterService { const titleId: string = child?.snapshot?.data?.titleId; const rawTitle: string = child?.snapshot?.data?.title; + + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression const updateUrl = !child?.snapshot?.data?.doNotSaveUrl ?? true; if (titleId != null || rawTitle != null) { diff --git a/eslint.config.mjs b/eslint.config.mjs index aa0b475da0b..5a4874457a0 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,5 +1,5 @@ // @ts-check - +import { fixupPluginRules } from "@eslint/compat"; import eslint from "@eslint/js"; import tseslint from "typescript-eslint"; import angular from "angular-eslint"; @@ -31,8 +31,8 @@ export default tseslint.config( reportUnusedDisableDirectives: "error", }, plugins: { - rxjs: rxjs, - "rxjs-angular": angularRxjs, + rxjs: fixupPluginRules(rxjs), + "rxjs-angular": fixupPluginRules(angularRxjs), "@bitwarden/platform": platformPlugins, }, languageOptions: { diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts index 84ebf981f03..ecfeb10dcda 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts @@ -89,6 +89,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { ) { this.supportsDeviceTrust$ = this.userDecryptionOptionsService.userDecryptionOptions$.pipe( map((options) => { + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression return options?.trustedDeviceOption != null ?? false; }), ); @@ -97,6 +99,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { supportsDeviceTrustByUserId$(userId: UserId): Observable { return this.userDecryptionOptionsService.userDecryptionOptionsById$(userId).pipe( map((options) => { + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression return options?.trustedDeviceOption != null ?? false; }), ); diff --git a/libs/eslint/fix-jsdom.ts b/libs/eslint/fix-jsdom.ts new file mode 100644 index 00000000000..f7c29a62723 --- /dev/null +++ b/libs/eslint/fix-jsdom.ts @@ -0,0 +1,10 @@ +import JSDOMEnvironment from "jest-environment-jsdom"; + +export default class FixJSDOMEnvironment extends JSDOMEnvironment { + constructor(...args: ConstructorParameters) { + super(...args); + + // FIXME https://github.com/jsdom/jsdom/issues/3363 + this.global.structuredClone = structuredClone; + } +} diff --git a/libs/eslint/jest.config.js b/libs/eslint/jest.config.js index 67436ff906e..5acadb023e4 100644 --- a/libs/eslint/jest.config.js +++ b/libs/eslint/jest.config.js @@ -3,6 +3,7 @@ const sharedConfig = require("../../libs/shared/jest.config.angular"); /** @type {import('jest').Config} */ module.exports = { ...sharedConfig, + testEnvironment: "./fix-jsdom.ts", testMatch: ["**/+(*.)+(spec).+(mjs)"], displayName: "libs/eslint tests", preset: "jest-preset-angular", diff --git a/libs/eslint/test.setup.mjs b/libs/eslint/test.setup.mjs index 45de6fcf3e1..19f35df9e7f 100644 --- a/libs/eslint/test.setup.mjs +++ b/libs/eslint/test.setup.mjs @@ -1,5 +1,3 @@ -/* eslint-disable no-undef */ - import { clearImmediate, setImmediate } from "node:timers"; Object.defineProperties(globalThis, { diff --git a/libs/eslint/tsconfig.json b/libs/eslint/tsconfig.json index eb2bb889d1a..bbf065886c4 100644 --- a/libs/eslint/tsconfig.json +++ b/libs/eslint/tsconfig.json @@ -1,5 +1,6 @@ { "extends": "../shared/tsconfig", "compilerOptions": {}, - "exclude": ["node_modules", "dist"] + "exclude": ["node_modules", "dist"], + "files": ["empty.ts"] } diff --git a/libs/importer/src/importers/dashlane/dashlane-csv-importer.ts b/libs/importer/src/importers/dashlane/dashlane-csv-importer.ts index 5b282fb466c..2ead8c4bb21 100644 --- a/libs/importer/src/importers/dashlane/dashlane-csv-importer.ts +++ b/libs/importer/src/importers/dashlane/dashlane-csv-importer.ts @@ -92,7 +92,7 @@ export class DashlaneCsvImporter extends BaseImporter implements Importer { this.parseIdRecord(cipher, row); } - if ((rowKeys[0] === "type") != null && rowKeys[1] === "title") { + if (rowKeys[0] === "type" && rowKeys[1] === "title") { this.parsePersonalInformationRecord(cipher, row); } diff --git a/package-lock.json b/package-lock.json index f0dd03b5e74..691705cc280 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,6 +81,7 @@ "@compodoc/compodoc": "1.1.26", "@electron/notarize": "2.5.0", "@electron/rebuild": "3.7.2", + "@eslint/compat": "1.2.9", "@lit-labs/signals": "0.1.2", "@ngtools/webpack": "18.2.19", "@storybook/addon-a11y": "8.6.12", @@ -136,7 +137,7 @@ "electron-reload": "2.0.0-alpha.1", "electron-store": "8.2.0", "electron-updater": "6.6.4", - "eslint": "8.57.1", + "eslint": "9.26.0", "eslint-config-prettier": "10.1.2", "eslint-import-resolver-typescript": "4.3.4", "eslint-plugin-import": "2.31.0", @@ -398,14 +399,14 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1902.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.12.tgz", - "integrity": "sha512-LfUc7k84YL290hAxsG+FvjQpXugQXyw5aDzrQQB4iTYhBgaABu2aaNOU4eu3JH+F8NeXd2EBF/YMr2LDSkYlMw==", + "version": "0.1902.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.13.tgz", + "integrity": "sha512-ZMj+PjK22Ph2U8usG6L7LqEfvWlbaOvmiWXSrEt9YiC9QJt6rsumCkOgUIsmHQtucm/lK+9CMtyYdwH2fYycjg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@angular-devkit/core": "19.2.12", + "@angular-devkit/core": "19.2.13", "rxjs": "7.8.1" }, "engines": { @@ -762,6 +763,20 @@ "@types/send": "*" } }, + "node_modules/@angular-devkit/build-angular/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/agent-base": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", @@ -828,6 +843,48 @@ "webpack": ">=5" } }, + "node_modules/@angular-devkit/build-angular/node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/@angular-devkit/build-angular/node_modules/browserslist": { "version": "4.24.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", @@ -899,6 +956,19 @@ "node": ">= 6" } }, + "node_modules/@angular-devkit/build-angular/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -906,6 +976,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@angular-devkit/build-angular/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@angular-devkit/build-angular/node_modules/copy-webpack-plugin": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", @@ -968,6 +1055,116 @@ "node": ">=4.0" } }, + "node_modules/@angular-devkit/build-angular/node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular-devkit/build-angular/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular-devkit/build-angular/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -1003,6 +1200,19 @@ "node": ">= 14" } }, + "node_modules/@angular-devkit/build-angular/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/immutable": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", @@ -1010,6 +1220,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@angular-devkit/build-angular/node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -1076,6 +1296,62 @@ "dev": true, "license": "ISC" }, + "node_modules/@angular-devkit/build-angular/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/mini-css-extract-plugin": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", @@ -1097,6 +1373,16 @@ "webpack": "^5.0.0" } }, + "node_modules/@angular-devkit/build-angular/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/open": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", @@ -1133,6 +1419,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@angular-devkit/build-angular/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@angular-devkit/build-angular/node_modules/postcss": { "version": "8.4.41", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", @@ -1162,6 +1455,38 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/@angular-devkit/build-angular/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -1263,6 +1588,88 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular-devkit/build-angular/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/webpack": { "version": "5.94.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", @@ -1578,9 +1985,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "19.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.12.tgz", - "integrity": "sha512-v5pdfZHZ8MTZozfpkhKoPFBpXQW+2GFbTfdyis8FBtevJWCbIsCR3xhodgI4jwzkSEAraN4oVtWvSytdNyBC6A==", + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.13.tgz", + "integrity": "sha512-iq73hE5Uvms1w3uMUSk4i4NDXDMQ863VAifX8LOTadhG6U0xISjNJ11763egVCxQmaKmg7zbG4rda88wHJATzA==", "dev": true, "license": "MIT", "peer": true, @@ -6577,17 +6984,97 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/compat": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.9.tgz", + "integrity": "sha512-gCdSY54n7k+driCadyMNv8JSPzYLeDVM/ikZRtvtROBpRdFSkS8W9A82MqsaY7lZuwL0wiapgD0NT1xT0hyJsA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -6595,7 +7082,7 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -6630,16 +7117,13 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6675,27 +7159,38 @@ "node": "*" } }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "version": "9.26.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz", + "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==", "dev": true, "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.13.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@figspec/components": { @@ -6796,44 +7291,42 @@ "@hapi/hoek": "^9.0.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "Apache-2.0", "engines": { - "node": "*" + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, "node_modules/@humanwhocodes/module-importer": { @@ -6850,13 +7343,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "BSD-3-Clause" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@inquirer/checkbox": { "version": "2.5.0", @@ -8126,6 +8625,66 @@ } } }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.0.tgz", + "integrity": "sha512-m//7RlINx1F3sz3KqwY1WWzVgTcYX52HYk4bJ1hkBXV3zccAEth+jRvG8DBRrdaQuRsPAJOx2MH3zaHNCKL7Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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/@modelcontextprotocol/sdk/node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/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==", + "dev": true, + "license": "MIT" + }, "node_modules/@msgpack/msgpack": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", @@ -9897,6 +10456,16 @@ "encoding": "^0.1.13" } }, + "node_modules/@sigstore/sign/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@sigstore/sign/node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -10540,6 +11109,29 @@ } } }, + "node_modules/@storybook/builder-webpack5/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@storybook/builder-webpack5/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@storybook/builder-webpack5/node_modules/style-loader": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", @@ -10883,9 +11475,9 @@ } }, "node_modules/@swc/core": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.24.tgz", - "integrity": "sha512-MaQEIpfcEMzx3VWWopbofKJvaraqmL6HbLlw2bFZ7qYqYw3rkhM0cQVEgyzbHtTWwCwPMFZSC2DUbhlZgrMfLg==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.29.tgz", + "integrity": "sha512-g4mThMIpWbNhV8G2rWp5a5/Igv8/2UFRJx2yImrLGMgrDDYZIopqZ/z0jZxDgqNA1QDx93rpwNF7jGsxVWcMlA==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -10901,16 +11493,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.11.24", - "@swc/core-darwin-x64": "1.11.24", - "@swc/core-linux-arm-gnueabihf": "1.11.24", - "@swc/core-linux-arm64-gnu": "1.11.24", - "@swc/core-linux-arm64-musl": "1.11.24", - "@swc/core-linux-x64-gnu": "1.11.24", - "@swc/core-linux-x64-musl": "1.11.24", - "@swc/core-win32-arm64-msvc": "1.11.24", - "@swc/core-win32-ia32-msvc": "1.11.24", - "@swc/core-win32-x64-msvc": "1.11.24" + "@swc/core-darwin-arm64": "1.11.29", + "@swc/core-darwin-x64": "1.11.29", + "@swc/core-linux-arm-gnueabihf": "1.11.29", + "@swc/core-linux-arm64-gnu": "1.11.29", + "@swc/core-linux-arm64-musl": "1.11.29", + "@swc/core-linux-x64-gnu": "1.11.29", + "@swc/core-linux-x64-musl": "1.11.29", + "@swc/core-win32-arm64-msvc": "1.11.29", + "@swc/core-win32-ia32-msvc": "1.11.29", + "@swc/core-win32-x64-msvc": "1.11.29" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -10922,9 +11514,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.24.tgz", - "integrity": "sha512-dhtVj0PC1APOF4fl5qT2neGjRLgHAAYfiVP8poJelhzhB/318bO+QCFWAiimcDoyMgpCXOhTp757gnoJJrheWA==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.29.tgz", + "integrity": "sha512-whsCX7URzbuS5aET58c75Dloby3Gtj/ITk2vc4WW6pSDQKSPDuONsIcZ7B2ng8oz0K6ttbi4p3H/PNPQLJ4maQ==", "cpu": [ "arm64" ], @@ -10939,9 +11531,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.24.tgz", - "integrity": "sha512-H/3cPs8uxcj2Fe3SoLlofN5JG6Ny5bl8DuZ6Yc2wr7gQFBmyBkbZEz+sPVgsID7IXuz7vTP95kMm1VL74SO5AQ==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.29.tgz", + "integrity": "sha512-S3eTo/KYFk+76cWJRgX30hylN5XkSmjYtCBnM4jPLYn7L6zWYEPajsFLmruQEiTEDUg0gBEWLMNyUeghtswouw==", "cpu": [ "x64" ], @@ -10956,9 +11548,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.24.tgz", - "integrity": "sha512-PHJgWEpCsLo/NGj+A2lXZ2mgGjsr96ULNW3+T3Bj2KTc8XtMUkE8tmY2Da20ItZOvPNC/69KroU7edyo1Flfbw==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.29.tgz", + "integrity": "sha512-o9gdshbzkUMG6azldHdmKklcfrcMx+a23d/2qHQHPDLUPAN+Trd+sDQUYArK5Fcm7TlpG4sczz95ghN0DMkM7g==", "cpu": [ "arm" ], @@ -10973,9 +11565,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.24.tgz", - "integrity": "sha512-C2FJb08+n5SD4CYWCTZx1uR88BN41ZieoHvI8A55hfVf2woT8+6ZiBzt74qW2g+ntZ535Jts5VwXAKdu41HpBg==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.29.tgz", + "integrity": "sha512-sLoaciOgUKQF1KX9T6hPGzvhOQaJn+3DHy4LOHeXhQqvBgr+7QcZ+hl4uixPKTzxk6hy6Hb0QOvQEdBAAR1gXw==", "cpu": [ "arm64" ], @@ -10990,9 +11582,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.24.tgz", - "integrity": "sha512-ypXLIdszRo0re7PNNaXN0+2lD454G8l9LPK/rbfRXnhLWDBPURxzKlLlU/YGd2zP98wPcVooMmegRSNOKfvErw==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.29.tgz", + "integrity": "sha512-PwjB10BC0N+Ce7RU/L23eYch6lXFHz7r3NFavIcwDNa/AAqywfxyxh13OeRy+P0cg7NDpWEETWspXeI4Ek8otw==", "cpu": [ "arm64" ], @@ -11007,9 +11599,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.24.tgz", - "integrity": "sha512-IM7d+STVZD48zxcgo69L0yYptfhaaE9cMZ+9OoMxirNafhKKXwoZuufol1+alEFKc+Wbwp+aUPe/DeWC/Lh3dg==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.29.tgz", + "integrity": "sha512-i62vBVoPaVe9A3mc6gJG07n0/e7FVeAvdD9uzZTtGLiuIfVfIBta8EMquzvf+POLycSk79Z6lRhGPZPJPYiQaA==", "cpu": [ "x64" ], @@ -11024,9 +11616,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.24.tgz", - "integrity": "sha512-DZByJaMVzSfjQKKQn3cqSeqwy6lpMaQDQQ4HPlch9FWtDx/dLcpdIhxssqZXcR2rhaQVIaRQsCqwV6orSDGAGw==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.29.tgz", + "integrity": "sha512-YER0XU1xqFdK0hKkfSVX1YIyCvMDI7K07GIpefPvcfyNGs38AXKhb2byySDjbVxkdl4dycaxxhRyhQ2gKSlsFQ==", "cpu": [ "x64" ], @@ -11041,9 +11633,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.24.tgz", - "integrity": "sha512-Q64Ytn23y9aVDKN5iryFi8mRgyHw3/kyjTjT4qFCa8AEb5sGUuSj//AUZ6c0J7hQKMHlg9do5Etvoe61V98/JQ==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.29.tgz", + "integrity": "sha512-po+WHw+k9g6FAg5IJ+sMwtA/fIUL3zPQ4m/uJgONBATCVnDDkyW6dBA49uHNVtSEvjvhuD8DVWdFP847YTcITw==", "cpu": [ "arm64" ], @@ -11058,9 +11650,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.24.tgz", - "integrity": "sha512-9pKLIisE/Hh2vJhGIPvSoTK4uBSPxNVyXHmOrtdDot4E1FUUI74Vi8tFdlwNbaj8/vusVnb8xPXsxF1uB0VgiQ==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.29.tgz", + "integrity": "sha512-h+NjOrbqdRBYr5ItmStmQt6x3tnhqgwbj9YxdGPepbTDamFv7vFnhZR0YfB3jz3UKJ8H3uGJ65Zw1VsC+xpFkg==", "cpu": [ "ia32" ], @@ -11075,9 +11667,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.11.24", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.24.tgz", - "integrity": "sha512-sybnXtOsdB+XvzVFlBVGgRHLqp3yRpHK7CrmpuDKszhj/QhmsaZzY/GHSeALlMtLup13M0gqbcQvsTNlAHTg3w==", + "version": "1.11.29", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.29.tgz", + "integrity": "sha512-Q8cs2BDV9wqDvqobkXOYdC+pLUSEpX/KvI0Dgfun1F+LzuLotRFuDhrvkU9ETJA6OnD2+Fn/ieHgloiKA/Mn/g==", "cpu": [ "x64" ], @@ -11505,9 +12097,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true, "license": "MIT" }, @@ -11827,9 +12419,9 @@ } }, "node_modules/@types/lodash": { - "version": "4.17.16", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz", - "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==", + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.17.tgz", + "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==", "dev": true, "license": "MIT" }, @@ -11928,6 +12520,29 @@ "node": ">= 6" } }, + "node_modules/@types/node-fetch/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@types/node-fetch/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@types/node-forge": { "version": "1.3.11", "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", @@ -12709,13 +13324,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, "node_modules/@unrs/resolver-binding-darwin-arm64": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.2.tgz", @@ -13524,13 +14132,14 @@ } }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" @@ -15099,87 +15708,24 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/body-parser/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, "node_modules/bonjour-service": { @@ -15713,6 +16259,27 @@ "node": ">= 6.0.0" } }, + "node_modules/cache-content-type/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cache-content-type/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -16357,6 +16924,76 @@ "node": ">=8.0.0" } }, + "node_modules/co-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co-body/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/co-body/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/co-body/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/co-body/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/co-body/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/code-block-writer": { "version": "13.0.3", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", @@ -16812,9 +17449,10 @@ "license": "MIT" }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -16839,9 +17477,9 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", "engines": { @@ -16849,11 +17487,14 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/cookies": { "version": "0.9.1", @@ -18008,16 +18649,16 @@ } }, "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=0.10.0" } }, "node_modules/dom-accessibility-api": { @@ -18444,9 +19085,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.155", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", - "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==", + "version": "1.5.157", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.157.tgz", + "integrity": "sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w==", "dev": true, "license": "ISC" }, @@ -18497,9 +19138,9 @@ } }, "node_modules/electron/node_modules/@types/node": { - "version": "20.17.48", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.48.tgz", - "integrity": "sha512-KpSfKOHPsiSC4IkZeu2LsusFwExAIVGkhG1KkbaBMLwau0uMhj0fCrvyg9ddM2sAvd+gtiBJLir4LAw1MNMIaw==", + "version": "20.17.50", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.50.tgz", + "integrity": "sha512-Mxiq0ULv/zo1OzOhwPqOA13I81CV/W3nvd3ChtQZRT5Cwz3cr0FKo/wMSsbTqL3EXpaBAEQhva2B8ByRkOIh9A==", "dev": true, "license": "MIT", "dependencies": { @@ -18552,9 +19193,10 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -18699,9 +19341,9 @@ } }, "node_modules/es-abstract": { - "version": "1.23.9", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", - "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "version": "1.23.10", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.10.tgz", + "integrity": "sha512-MtUbM072wlJNyeYAe0mhzrD+M6DIJa96CZAOBBrhDbgKnB4MApIKefcyAB1eOdYn8cUNZgvwBvEzdoAYsxgEIw==", "dev": true, "license": "MIT", "dependencies": { @@ -18709,18 +19351,18 @@ "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", - "call-bound": "^1.0.3", + "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.0", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", @@ -18736,13 +19378,13 @@ "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.0", + "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.3", + "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.3", + "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", @@ -18755,7 +19397,7 @@ "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.18" + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -18990,60 +19632,66 @@ } }, "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "version": "9.26.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.26.0.tgz", + "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.13.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.26.0", + "@eslint/plugin-kit": "^0.2.8", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", + "@humanwhocodes/retry": "^0.4.2", + "@modelcontextprotocol/sdk": "^1.8.0", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "zod": "^3.24.2" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-prettier": { @@ -19214,19 +19862,6 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/eslint-plugin-import/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -19383,39 +20018,19 @@ "concat-map": "0.0.1" } }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, + "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -19446,32 +20061,32 @@ "node": "*" } }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -19607,6 +20222,16 @@ "node": ">=12.0.0" } }, + "node_modules/eventsource-parser": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", + "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -19715,192 +20340,82 @@ "license": "Apache-2.0" }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "dev": true, "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/express/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "node_modules/express/node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "dev": true, "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { "node": ">= 0.8" } }, - "node_modules/express/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/express/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/express/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/express/node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express/node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -20160,16 +20675,16 @@ } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/filelist": { @@ -20236,6 +20751,16 @@ "ms": "2.0.0" } }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -20243,6 +20768,29 @@ "dev": true, "license": "MIT" }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/find-cache-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", @@ -20355,81 +20903,17 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flat-cache/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/flat-cache/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/flat-cache/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=16" } }, "node_modules/flatted": { @@ -20729,6 +21213,27 @@ "node": ">= 6" } }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -20769,12 +21274,13 @@ "license": "ISC" }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/from": { @@ -21145,9 +21651,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", - "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -21937,6 +22443,15 @@ "node": ">= 0.6" } }, + "node_modules/http-assert/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/http-auth": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-4.1.9.tgz", @@ -22003,15 +22518,6 @@ "node": ">= 0.8" } }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/http-parser-js": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", @@ -22566,13 +23072,13 @@ } }, "node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true, "license": "MIT", "engines": { - "node": ">= 10" + "node": ">= 0.10" } }, "node_modules/is-arguments": { @@ -22985,16 +23491,6 @@ "node": ">=8" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -23469,9 +23965,9 @@ } }, "node_modules/jackspeak": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", - "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -25643,6 +26139,49 @@ "node": ">=8.0.0" } }, + "node_modules/koa-bodyparser/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-bodyparser/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-bodyparser/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-bodyparser/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/koa-compose": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", @@ -25678,6 +26217,49 @@ "streaming-json-stringify": "3" } }, + "node_modules/koa/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/koa/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/koa/node_modules/http-errors": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", @@ -25703,6 +26285,67 @@ "node": ">= 0.6" } }, + "node_modules/koa/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/launch-editor": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", @@ -26967,6 +27610,16 @@ "node": ">=8" } }, + "node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/make-fetch-happen/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -27259,12 +27912,13 @@ } }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/memfs": { @@ -27291,11 +27945,14 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "dev": true, "license": "MIT", + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -27957,21 +28614,23 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" @@ -28351,6 +29010,19 @@ "dev": true, "license": "MIT" }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/mrmime": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", @@ -28404,6 +29076,7 @@ "version": "1.4.5-lts.2", "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", "license": "MIT", "dependencies": { "append-field": "^1.0.0", @@ -28418,6 +29091,36 @@ "node": ">= 6.0.0" } }, + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/multer/node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -28430,6 +29133,19 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/multicast-dns": { "version": "7.2.5", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", @@ -28582,9 +29298,10 @@ } }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -28966,6 +29683,16 @@ "encoding": "^0.1.13" } }, + "node_modules/node-gyp/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-gyp/node_modules/nopt": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", @@ -29462,6 +30189,16 @@ "encoding": "^0.1.13" } }, + "node_modules/npm-registry-fetch/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/npm-registry-fetch/node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -30202,9 +30939,9 @@ } }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -31194,6 +31931,16 @@ "nice-napi": "^1.0.2" } }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/pkg-dir": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", @@ -32052,16 +32799,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -32227,32 +32964,21 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.4.24", + "iconv-lite": "0.6.3", "unpipe": "1.0.0" }, "engines": { "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -33136,6 +33862,47 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/rrweb-cssom": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", @@ -33545,72 +34312,6 @@ "node": ">= 18" } }, - "node_modules/send/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/serialize-error": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", @@ -33671,6 +34372,20 @@ "node": ">= 0.8.0" } }, + "node_modules/serve-index/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-index/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -33714,6 +34429,29 @@ "dev": true, "license": "ISC" }, + "node_modules/serve-index/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-index/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -33721,6 +34459,16 @@ "dev": true, "license": "MIT" }, + "node_modules/serve-index/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-index/node_modules/setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", @@ -33728,118 +34476,30 @@ "dev": true, "license": "ISC" }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "dev": true, "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-static/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/serve-static/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/serve-static/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static/node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" + "node": ">= 18" } }, "node_modules/set-blocking": { @@ -34610,12 +35270,12 @@ } }, "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/steno": { @@ -35309,9 +35969,9 @@ } }, "node_modules/tar-fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", - "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", "dev": true, "license": "MIT", "dependencies": { @@ -35551,13 +36211,6 @@ "node": "*" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -36244,6 +36897,16 @@ "encoding": "^0.1.13" } }, + "node_modules/tuf-js/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/tuf-js/node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -36360,13 +37023,15 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dev": true, "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { "node": ">= 0.6" @@ -38024,17 +38689,27 @@ "url": "https://github.com/sponsors/streamich" } }, - "node_modules/webpack-dev-middleware/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/webpack-dev-middleware/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", "dependencies": { - "ee-first": "1.1.1" + "mime-db": "1.52.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "node_modules/webpack-dev-server": { @@ -38120,6 +38795,45 @@ "@types/send": "*" } }, + "node_modules/webpack-dev-server/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/webpack-dev-server/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -38145,6 +38859,53 @@ "fsevents": "~2.3.2" } }, + "node_modules/webpack-dev-server/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-dev-server/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", @@ -38158,6 +38919,82 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/webpack-dev-server/node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/webpack-dev-server/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/webpack-dev-server/node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -38196,6 +39033,29 @@ } } }, + "node_modules/webpack-dev-server/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/webpack-dev-server/node_modules/is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -38225,6 +39085,72 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/webpack-dev-server/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/webpack-dev-server/node_modules/open": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", @@ -38244,6 +39170,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/webpack-dev-server/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, "node_modules/webpack-dev-server/node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -38257,6 +39190,38 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/webpack-dev-server/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/webpack-dev-server/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/webpack-dev-server/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -38270,6 +39235,71 @@ "node": ">=8.10.0" } }, + "node_modules/webpack-dev-server/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/webpack-dev-server/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/webpack-dev-server/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/webpack-hot-middleware": { "version": "2.26.1", "resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.26.1.tgz", @@ -38346,13 +39376,6 @@ "dev": true, "license": "MIT" }, - "node_modules/webpack/node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/webpack/node_modules/browserslist": { "version": "4.24.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", @@ -38417,6 +39440,29 @@ "dev": true, "license": "MIT" }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -38982,6 +40028,26 @@ "node": "*" } }, + "node_modules/zod": { + "version": "3.25.23", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.23.tgz", + "integrity": "sha512-Od2bdMosahjSrSgJtakrwjMDb1zM1A3VIHCPGveZt/3/wlrTWBya2lmEh2OYe4OIu8mPTmmr0gnLHIWQXdtWBg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + }, "node_modules/zone.js": { "version": "0.14.10", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz", diff --git a/package.json b/package.json index fe7f69ff40d..84164696f46 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@compodoc/compodoc": "1.1.26", "@electron/notarize": "2.5.0", "@electron/rebuild": "3.7.2", + "@eslint/compat": "1.2.9", "@lit-labs/signals": "0.1.2", "@ngtools/webpack": "18.2.19", "@storybook/addon-a11y": "8.6.12", @@ -102,7 +103,7 @@ "electron-reload": "2.0.0-alpha.1", "electron-store": "8.2.0", "electron-updater": "6.6.4", - "eslint": "8.57.1", + "eslint": "9.26.0", "eslint-config-prettier": "10.1.2", "eslint-import-resolver-typescript": "4.3.4", "eslint-plugin-import": "2.31.0", @@ -207,6 +208,12 @@ "zxcvbn": "4.4.2" }, "overrides": { + "eslint-plugin-rxjs": { + "eslint": "$eslint" + }, + "eslint-plugin-rxjs-angular": { + "eslint": "$eslint" + }, "tailwindcss": "$tailwindcss", "@storybook/angular": { "zone.js": "$zone.js" From 88bc7625218028cdea3d73663a00f8d24133ba6e Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Tue, 27 May 2025 14:51:40 -0400 Subject: [PATCH 169/245] PM-16645 (#14649) --- .../src/autofill/services/autofill.service.ts | 248 +----------------- libs/common/src/enums/feature-flag.enum.ts | 2 - 2 files changed, 1 insertion(+), 249 deletions(-) diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 525010bacc1..fdd881c2760 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -1579,252 +1579,6 @@ export default class AutofillService implements AutofillServiceInterface { return [expectedDateFormat, dateFormatPatterns]; } - /** - * Generates the autofill script for the specified page details and identify cipher item. - * @param {AutofillScript} fillScript - * @param {AutofillPageDetails} pageDetails - * @param {{[p: string]: AutofillField}} filledFields - * @param {GenerateFillScriptOptions} options - * @returns {AutofillScript} - * @private - */ - private async generateIdentityFillScript( - fillScript: AutofillScript, - pageDetails: AutofillPageDetails, - filledFields: { [id: string]: AutofillField }, - options: GenerateFillScriptOptions, - ): Promise { - if (await this.configService.getFeatureFlag(FeatureFlag.GenerateIdentityFillScriptRefactor)) { - return this._generateIdentityFillScript(fillScript, pageDetails, filledFields, options); - } - - if (!options.cipher.identity) { - return null; - } - - const fillFields: { [id: string]: AutofillField } = {}; - - pageDetails.fields.forEach((f) => { - if ( - AutofillService.isExcludedFieldType(f, AutoFillConstants.ExcludedAutofillTypes) || - ["current-password", "new-password"].includes(f.autoCompleteType) - ) { - return; - } - - for (let i = 0; i < IdentityAutoFillConstants.IdentityAttributes.length; i++) { - const attr = IdentityAutoFillConstants.IdentityAttributes[i]; - // eslint-disable-next-line - if (!f.hasOwnProperty(attr) || !f[attr] || !f.viewable) { - continue; - } - - // ref https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill - // ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/ - if ( - !fillFields.name && - AutofillService.isFieldMatch( - f[attr], - IdentityAutoFillConstants.FullNameFieldNames, - IdentityAutoFillConstants.FullNameFieldNameValues, - ) - ) { - fillFields.name = f; - break; - } else if ( - !fillFields.firstName && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.FirstnameFieldNames) - ) { - fillFields.firstName = f; - break; - } else if ( - !fillFields.middleName && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.MiddlenameFieldNames) - ) { - fillFields.middleName = f; - break; - } else if ( - !fillFields.lastName && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.LastnameFieldNames) - ) { - fillFields.lastName = f; - break; - } else if ( - !fillFields.title && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.TitleFieldNames) - ) { - fillFields.title = f; - break; - } else if ( - !fillFields.email && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.EmailFieldNames) - ) { - fillFields.email = f; - break; - } else if ( - !fillFields.address1 && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address1FieldNames) - ) { - fillFields.address1 = f; - break; - } else if ( - !fillFields.address2 && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address2FieldNames) - ) { - fillFields.address2 = f; - break; - } else if ( - !fillFields.address3 && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address3FieldNames) - ) { - fillFields.address3 = f; - break; - } else if ( - !fillFields.address && - AutofillService.isFieldMatch( - f[attr], - IdentityAutoFillConstants.AddressFieldNames, - IdentityAutoFillConstants.AddressFieldNameValues, - ) - ) { - fillFields.address = f; - break; - } else if ( - !fillFields.postalCode && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PostalCodeFieldNames) - ) { - fillFields.postalCode = f; - break; - } else if ( - !fillFields.city && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CityFieldNames) - ) { - fillFields.city = f; - break; - } else if ( - !fillFields.state && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.StateFieldNames) - ) { - fillFields.state = f; - break; - } else if ( - !fillFields.country && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CountryFieldNames) - ) { - fillFields.country = f; - break; - } else if ( - !fillFields.phone && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PhoneFieldNames) - ) { - fillFields.phone = f; - break; - } else if ( - !fillFields.username && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.UserNameFieldNames) - ) { - fillFields.username = f; - break; - } else if ( - !fillFields.company && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CompanyFieldNames) - ) { - fillFields.company = f; - break; - } - } - }); - - const identity = options.cipher.identity; - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "title"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "firstName"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "middleName"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "lastName"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "address1"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "address2"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "address3"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "city"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "postalCode"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "company"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "email"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "phone"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "username"); - - let filledState = false; - if (fillFields.state && identity.state && identity.state.length > 2) { - const stateLower = identity.state.toLowerCase(); - const isoState = - IdentityAutoFillConstants.IsoStates[stateLower] || - IdentityAutoFillConstants.IsoProvinces[stateLower]; - if (isoState) { - filledState = true; - this.makeScriptActionWithValue(fillScript, isoState, fillFields.state, filledFields); - } - } - - if (!filledState) { - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "state"); - } - - let filledCountry = false; - if (fillFields.country && identity.country && identity.country.length > 2) { - const countryLower = identity.country.toLowerCase(); - const isoCountry = IdentityAutoFillConstants.IsoCountries[countryLower]; - if (isoCountry) { - filledCountry = true; - this.makeScriptActionWithValue(fillScript, isoCountry, fillFields.country, filledFields); - } - } - - if (!filledCountry) { - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "country"); - } - - if (fillFields.name && (identity.firstName || identity.lastName)) { - let fullName = ""; - if (AutofillService.hasValue(identity.firstName)) { - fullName = identity.firstName; - } - if (AutofillService.hasValue(identity.middleName)) { - if (fullName !== "") { - fullName += " "; - } - fullName += identity.middleName; - } - if (AutofillService.hasValue(identity.lastName)) { - if (fullName !== "") { - fullName += " "; - } - fullName += identity.lastName; - } - - this.makeScriptActionWithValue(fillScript, fullName, fillFields.name, filledFields); - } - - if (fillFields.address && AutofillService.hasValue(identity.address1)) { - let address = ""; - if (AutofillService.hasValue(identity.address1)) { - address = identity.address1; - } - if (AutofillService.hasValue(identity.address2)) { - if (address !== "") { - address += ", "; - } - address += identity.address2; - } - if (AutofillService.hasValue(identity.address3)) { - if (address !== "") { - address += ", "; - } - address += identity.address3; - } - - this.makeScriptActionWithValue(fillScript, address, fillFields.address, filledFields); - } - - return fillScript; - } - /** * Generates the autofill script for the specified page details and identity cipher item. * @@ -1833,7 +1587,7 @@ export default class AutofillService implements AutofillServiceInterface { * @param filledFields - The fields that have already been filled, passed between method references * @param options - Contains data used to fill cipher items */ - private _generateIdentityFillScript( + private generateIdentityFillScript( fillScript: AutofillScript, pageDetails: AutofillPageDetails, filledFields: { [id: string]: AutofillField }, diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index a5c5c615e70..8cd44f9d627 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -22,7 +22,6 @@ export enum FeatureFlag { BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain", DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2", EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill", - GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor", IdpAutoSubmitLogin = "idp-auto-submit-login", NotificationRefresh = "notification-refresh", UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", @@ -87,7 +86,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE, [FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE, [FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE, - [FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE, [FeatureFlag.IdpAutoSubmitLogin]: FALSE, [FeatureFlag.NotificationRefresh]: FALSE, [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, From cb770f5cd3381b38d34d502fb26f07659cf02d49 Mon Sep 17 00:00:00 2001 From: Patrick-Pimentel-Bitwarden Date: Tue, 27 May 2025 16:01:07 -0400 Subject: [PATCH 170/245] refactor(browser-platform-utils): Remove Deprecation and Fix Code (#14709) * refactor(browser-platform-utils): Remove Deprecation and Fix Code - Changed usages of firefox to private and moved the usages to the preferred public method and removed the deprecations. * fix(browser-platform-utils): Remove Deprecation and Fix Code - Tiny changes. * test(browser-platform-utils): Remove Deprecation and Fix Code - Fixed up test --- .../src/platform/listeners/update-badge.ts | 10 +++---- .../browser-platform-utils.service.spec.ts | 3 +- .../browser-platform-utils.service.ts | 30 ++++--------------- 3 files changed, 12 insertions(+), 31 deletions(-) diff --git a/apps/browser/src/platform/listeners/update-badge.ts b/apps/browser/src/platform/listeners/update-badge.ts index cd74b9ebf7d..c168ae44f3c 100644 --- a/apps/browser/src/platform/listeners/update-badge.ts +++ b/apps/browser/src/platform/listeners/update-badge.ts @@ -7,13 +7,13 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import MainBackground from "../../background/main.background"; import IconDetails from "../../vault/background/models/icon-details"; import { BrowserApi } from "../browser/browser-api"; -import { BrowserPlatformUtilsService } from "../services/platform-utils/browser-platform-utils.service"; export type BadgeOptions = { tab?: chrome.tabs.Tab; @@ -28,6 +28,7 @@ export class UpdateBadge { private badgeAction: typeof chrome.action | typeof chrome.browserAction; private sidebarAction: OperaSidebarAction | FirefoxSidebarAction; private win: Window & typeof globalThis; + private platformUtilsService: PlatformUtilsService; constructor(win: Window & typeof globalThis, services: MainBackground) { this.badgeAction = BrowserApi.getBrowserAction(); @@ -38,6 +39,7 @@ export class UpdateBadge { this.authService = services.authService; this.cipherService = services.cipherService; this.accountService = services.accountService; + this.platformUtilsService = services.platformUtilsService; } async run(opts?: { tabId?: number; windowId?: number }): Promise { @@ -129,7 +131,7 @@ export class UpdateBadge { 38: "/images/icon38" + iconSuffix + ".png", }, }; - if (windowId && BrowserPlatformUtilsService.isFirefox()) { + if (windowId && this.platformUtilsService.isFirefox()) { options.windowId = windowId; } @@ -204,9 +206,7 @@ export class UpdateBadge { } private get useSyncApiCalls() { - return ( - BrowserPlatformUtilsService.isFirefox() || BrowserPlatformUtilsService.isSafari(this.win) - ); + return this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari(); } private isOperaSidebar( diff --git a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts index 38166d10a08..f75e9cc29a5 100644 --- a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts +++ b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts @@ -126,12 +126,11 @@ describe("Browser Utils Service", () => { configurable: true, value: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0", }); - jest.spyOn(BrowserPlatformUtilsService, "isFirefox"); browserPlatformUtilsService.getDevice(); expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.FirefoxExtension); - expect(BrowserPlatformUtilsService.isFirefox).toHaveBeenCalledTimes(1); + expect(browserPlatformUtilsService.isFirefox()).toBe(true); }); }); diff --git a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts index 22708d8e425..4ae412fbda6 100644 --- a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts +++ b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts @@ -60,10 +60,7 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return ClientType.Browser; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ - static isFirefox(): boolean { + private static isFirefox(): boolean { return ( navigator.userAgent.indexOf(" Firefox/") !== -1 || navigator.userAgent.indexOf(" Gecko/") !== -1 @@ -74,9 +71,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.getDevice() === DeviceType.FirefoxExtension; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ private static isChrome(globalContext: Window | ServiceWorkerGlobalScope): boolean { return globalContext.chrome && navigator.userAgent.indexOf(" Chrome/") !== -1; } @@ -85,9 +79,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.getDevice() === DeviceType.ChromeExtension; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ private static isEdge(): boolean { return navigator.userAgent.indexOf(" Edg/") !== -1; } @@ -96,9 +87,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.getDevice() === DeviceType.EdgeExtension; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ private static isOpera(globalContext: Window | ServiceWorkerGlobalScope): boolean { return ( !!globalContext.opr?.addons || @@ -111,9 +99,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.getDevice() === DeviceType.OperaExtension; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ private static isVivaldi(): boolean { return navigator.userAgent.indexOf(" Vivaldi/") !== -1; } @@ -122,10 +107,7 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.getDevice() === DeviceType.VivaldiExtension; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ - static isSafari(globalContext: Window | ServiceWorkerGlobalScope): boolean { + private static isSafari(globalContext: Window | ServiceWorkerGlobalScope): boolean { // Opera masquerades as Safari, so make sure we're not there first return ( !BrowserPlatformUtilsService.isOpera(globalContext) && @@ -137,6 +119,10 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return navigator.userAgent.match("Version/([0-9.]*)")?.[1]; } + isSafari(): boolean { + return this.getDevice() === DeviceType.SafariExtension; + } + /** * Safari previous to version 16.1 had a bug which caused artifacts on hover in large extension popups. * https://bugs.webkit.org/show_bug.cgi?id=218704 @@ -151,10 +137,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return parts?.[0] < 16 || (parts?.[0] === 16 && parts?.[1] === 0); } - isSafari(): boolean { - return this.getDevice() === DeviceType.SafariExtension; - } - isIE(): boolean { return false; } From 50143a4b88dc170de92dd603b507545c334f2227 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Tue, 27 May 2025 16:50:39 -0400 Subject: [PATCH 171/245] pushes search text to a subject (#14880) use distinctUntilChanged to prevent duplicate filtering operations Run filtering outside angular zone to prevent change detection issues --- .../vault-search/vault-v2-search.component.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts index 32f5611f436..b68818454d1 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts @@ -1,8 +1,8 @@ import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, NgZone } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormsModule } from "@angular/forms"; -import { Subject, Subscription, debounceTime, filter } from "rxjs"; +import { Subject, Subscription, debounceTime, distinctUntilChanged, filter } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SearchModule } from "@bitwarden/components"; @@ -22,13 +22,16 @@ export class VaultV2SearchComponent { private searchText$ = new Subject(); - constructor(private vaultPopupItemsService: VaultPopupItemsService) { + constructor( + private vaultPopupItemsService: VaultPopupItemsService, + private ngZone: NgZone, + ) { this.subscribeToLatestSearchText(); this.subscribeToApplyFilter(); } onSearchTextChanged() { - this.vaultPopupItemsService.applyFilter(this.searchText); + this.searchText$.next(this.searchText); } subscribeToLatestSearchText(): Subscription { @@ -44,9 +47,13 @@ export class VaultV2SearchComponent { subscribeToApplyFilter(): Subscription { return this.searchText$ - .pipe(debounceTime(SearchTextDebounceInterval), takeUntilDestroyed()) + .pipe(debounceTime(SearchTextDebounceInterval), distinctUntilChanged(), takeUntilDestroyed()) .subscribe((data) => { - this.vaultPopupItemsService.applyFilter(data); + this.ngZone.runOutsideAngular(() => { + this.ngZone.run(() => { + this.vaultPopupItemsService.applyFilter(data); + }); + }); }); } } From 4fcc4793bbfff10fd3d2a228be004d28ae5eb838 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Wed, 28 May 2025 09:41:56 +1000 Subject: [PATCH 172/245] Add additional jsdoc to policyservice (#14934) --- .../policy/policy.service.abstraction.ts | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts b/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts index 68f9843c5bd..bf02872ed7c 100644 --- a/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/policy/policy.service.abstraction.ts @@ -7,6 +7,9 @@ import { MasterPasswordPolicyOptions } from "../../models/domain/master-password import { Policy } from "../../models/domain/policy"; import { ResetPasswordPolicyOptions } from "../../models/domain/reset-password-policy-options"; +/** + * The primary service for retrieving and evaluating policies from sync data. + */ export abstract class PolicyService { /** * All policies for the provided user from sync data. @@ -24,7 +27,7 @@ export abstract class PolicyService { abstract policiesByType$: (policyType: PolicyType, userId: UserId) => Observable; /** - * @returns true if a policy of the specified type applies to the specified user, otherwise false. + * @returns true if any policy of the specified type applies to the specified user, otherwise false. * A policy "applies" if it is enabled and the user is not exempt (e.g. because they are an Owner). * This does not take into account the policy's configuration - if that is important, use {@link policiesByType$} to get the * {@link Policy} objects and then filter by Policy.data. @@ -35,8 +38,12 @@ export abstract class PolicyService { /** * Combines all Master Password policies that apply to the user. + * If you are evaluating Master Password policies before the first sync has completed, + * you must supply your own `policies` value. + * @param userId The user against whom the policy needs to be enforced. + * @param policies The policies to be evaluated; if null or undefined, it will default to using policies from sync data. * @returns a set of options which represent the minimum Master Password settings that the user must - * comply with in order to comply with **all** Master Password policies. + * comply with in order to comply with **all** applicable Master Password policies. */ abstract masterPasswordPolicyOptions$: ( userId: UserId, @@ -62,7 +69,17 @@ export abstract class PolicyService { ) => [ResetPasswordPolicyOptions, boolean]; } +/** + * An "internal" extension of the `PolicyService` which allows the update of policy data in the local sync data. + * This does not update any policies on the server. + */ export abstract class InternalPolicyService extends PolicyService { + /** + * Upsert a policy in the local sync data. This does not update any policies on the server. + */ abstract upsert: (policy: PolicyData, userId: UserId) => Promise; + /** + * Replace a policy in the local sync data. This does not update any policies on the server. + */ abstract replace: (policies: { [id: string]: PolicyData }, userId: UserId) => Promise; } From d1fb37d696b98c64de0a291af868a56db99788cd Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 28 May 2025 15:00:30 +0200 Subject: [PATCH 173/245] [PM-17635] [PM-18601] Simplifying mocking and usage of the sdk (#14287) * feat: add our own custom deep mocker * feat: use new mock service in totp tests * feat: implement userClient mocking * chore: move mock files * feat: replace existing manual sdkService mocking * chore: rename to 'client' * chore: improve docs * feat: refactor sdkService to never return undefined BitwardenClient --- .../platform/abstractions/sdk/sdk.service.ts | 9 +- .../services/sdk/default-sdk.service.spec.ts | 8 +- .../services/sdk/default-sdk.service.ts | 5 +- .../src/platform/spec/mock-deep.spec.ts | 58 ++++ libs/common/src/platform/spec/mock-deep.ts | 271 ++++++++++++++++++ .../src/platform/spec/mock-sdk.service.ts | 81 ++++++ .../src/vault/services/totp.service.spec.ts | 31 +- .../src/services/import.service.spec.ts | 10 +- ...symmetric-key-regeneration.service.spec.ts | 25 +- 9 files changed, 444 insertions(+), 54 deletions(-) create mode 100644 libs/common/src/platform/spec/mock-deep.spec.ts create mode 100644 libs/common/src/platform/spec/mock-deep.ts create mode 100644 libs/common/src/platform/spec/mock-sdk.service.ts diff --git a/libs/common/src/platform/abstractions/sdk/sdk.service.ts b/libs/common/src/platform/abstractions/sdk/sdk.service.ts index 07dfb2aa0df..d629e4fe9fa 100644 --- a/libs/common/src/platform/abstractions/sdk/sdk.service.ts +++ b/libs/common/src/platform/abstractions/sdk/sdk.service.ts @@ -53,15 +53,18 @@ export abstract class SdkService { * This client can be used for operations that require a user context, such as retrieving ciphers * and operations involving crypto. It can also be used for operations that don't require a user context. * + * - If the user is not logged when the subscription is created, the observable will complete + * immediately with {@link UserNotLoggedInError}. + * - If the user is logged in, the observable will emit the client and complete whithout an error + * when the user logs out. + * * **WARNING:** Do not use `firstValueFrom(userClient$)`! Any operations on the client must be done within the observable. * The client will be destroyed when the observable is no longer subscribed to. * Please let platform know if you need a client that is not destroyed when the observable is no longer subscribed to. * * @param userId The user id for which to retrieve the client - * - * @throws {UserNotLoggedInError} If the user is not logged in */ - abstract userClient$(userId: UserId): Observable | undefined>; + abstract userClient$(userId: UserId): Observable>; /** * This method is used during/after an authentication procedure to set a new client for a specific user. diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts index 6531be58f05..70a08257471 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts @@ -132,15 +132,13 @@ describe("DefaultSdkService", () => { ); keyService.userKey$.calledWith(userId).mockReturnValue(userKey$); - const subject = new BehaviorSubject | undefined>(undefined); - service.userClient$(userId).subscribe(subject); - await new Promise(process.nextTick); + const userClientTracker = new ObservableTracker(service.userClient$(userId), false); + await userClientTracker.pauseUntilReceived(1); userKey$.next(undefined); - await new Promise(process.nextTick); + await userClientTracker.expectCompletion(); expect(mockClient.free).toHaveBeenCalledTimes(1); - expect(subject.value).toBe(undefined); }); }); diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 8e84642fb99..6be89a4b376 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -71,7 +71,7 @@ export class DefaultSdkService implements SdkService { private userAgent: string | null = null, ) {} - userClient$(userId: UserId): Observable | undefined> { + userClient$(userId: UserId): Observable> { return this.sdkClientOverrides.pipe( takeWhile((clients) => clients[userId] !== UnsetClient, false), map((clients) => { @@ -88,6 +88,7 @@ export class DefaultSdkService implements SdkService { return this.internalClient$(userId); }), + takeWhile((client) => client !== undefined, false), throwIfEmpty(() => new UserNotLoggedInError(userId)), ); } @@ -112,7 +113,7 @@ export class DefaultSdkService implements SdkService { * @param userId The user id for which to create the client * @returns An observable that emits the client for the user */ - private internalClient$(userId: UserId): Observable | undefined> { + private internalClient$(userId: UserId): Observable> { const cached = this.sdkClientCache.get(userId); if (cached !== undefined) { return cached; diff --git a/libs/common/src/platform/spec/mock-deep.spec.ts b/libs/common/src/platform/spec/mock-deep.spec.ts new file mode 100644 index 00000000000..535e02c11dd --- /dev/null +++ b/libs/common/src/platform/spec/mock-deep.spec.ts @@ -0,0 +1,58 @@ +import { mockDeep } from "./mock-deep"; + +class ToBeMocked { + property = "value"; + + method() { + return "method"; + } + + sub() { + return new SubToBeMocked(); + } +} + +class SubToBeMocked { + subProperty = "subValue"; + + sub() { + return new SubSubToBeMocked(); + } +} + +class SubSubToBeMocked { + subSubProperty = "subSubValue"; +} + +describe("deepMock", () => { + it("can mock properties", () => { + const mock = mockDeep(); + mock.property.replaceProperty("mocked value"); + expect(mock.property).toBe("mocked value"); + }); + + it("can mock methods", () => { + const mock = mockDeep(); + mock.method.mockReturnValue("mocked method"); + expect(mock.method()).toBe("mocked method"); + }); + + it("can mock sub-properties", () => { + const mock = mockDeep(); + mock.sub.mockDeep().subProperty.replaceProperty("mocked sub value"); + expect(mock.sub().subProperty).toBe("mocked sub value"); + }); + + it("can mock sub-sub-properties", () => { + const mock = mockDeep(); + mock.sub.mockDeep().sub.mockDeep().subSubProperty.replaceProperty("mocked sub-sub value"); + expect(mock.sub().sub().subSubProperty).toBe("mocked sub-sub value"); + }); + + it("returns the same mock object when calling mockDeep multiple times", () => { + const mock = mockDeep(); + const subMock1 = mock.sub.mockDeep(); + const subMock2 = mock.sub.mockDeep(); + expect(subMock1).toBe(subMock2); + }); +}); diff --git a/libs/common/src/platform/spec/mock-deep.ts b/libs/common/src/platform/spec/mock-deep.ts new file mode 100644 index 00000000000..89ef9a25451 --- /dev/null +++ b/libs/common/src/platform/spec/mock-deep.ts @@ -0,0 +1,271 @@ +// This is a modification of the code found in https://github.com/marchaos/jest-mock-extended +// to better support deep mocking of objects. + +// MIT License + +// Copyright (c) 2019 Marc McIntyre + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import { jest } from "@jest/globals"; +import { FunctionLike } from "jest-mock"; +import { calledWithFn, MatchersOrLiterals } from "jest-mock-extended"; +import { PartialDeep } from "type-fest"; + +type ProxiedProperty = string | number | symbol; + +export interface GlobalConfig { + // ignoreProps is required when we don't want to return anything for a mock (for example, when mocking a promise). + ignoreProps?: ProxiedProperty[]; +} + +const DEFAULT_CONFIG: GlobalConfig = { + ignoreProps: ["then"], +}; + +let GLOBAL_CONFIG = DEFAULT_CONFIG; + +export const JestMockExtended = { + DEFAULT_CONFIG, + configure: (config: GlobalConfig) => { + // Shallow merge so they can override anything they want. + GLOBAL_CONFIG = { ...DEFAULT_CONFIG, ...config }; + }, + resetConfig: () => { + GLOBAL_CONFIG = DEFAULT_CONFIG; + }, +}; + +export interface CalledWithMock extends jest.Mock { + calledWith: (...args: [...MatchersOrLiterals>]) => jest.Mock; +} + +export interface MockDeepMock { + mockDeep: () => DeepMockProxy; +} + +export interface ReplaceProperty { + /** + * mockDeep will by default return a jest.fn() for all properties, + * but this allows you to replace the property with a value. + * @param value The value to replace the property with. + */ + replaceProperty(value: T): void; +} + +export type _MockProxy = { + [K in keyof T]: T[K] extends FunctionLike ? T[K] & CalledWithMock : T[K]; +}; + +export type MockProxy = _MockProxy & T; + +export type _DeepMockProxy = { + // This supports deep mocks in the else branch + [K in keyof T]: T[K] extends (...args: infer A) => infer R + ? T[K] & CalledWithMock & MockDeepMock + : T[K] & ReplaceProperty & _DeepMockProxy; +}; + +// we intersect with T here instead of on the mapped type above to +// prevent immediate type resolution on a recursive type, this will +// help to improve performance for deeply nested recursive mocking +// at the same time, this intersection preserves private properties +export type DeepMockProxy = _DeepMockProxy & T; + +export type _DeepMockProxyWithFuncPropSupport = { + // This supports deep mocks in the else branch + [K in keyof T]: T[K] extends FunctionLike + ? CalledWithMock & DeepMockProxy + : DeepMockProxy; +}; + +export type DeepMockProxyWithFuncPropSupport = _DeepMockProxyWithFuncPropSupport & T; + +export interface MockOpts { + deep?: boolean; + fallbackMockImplementation?: (...args: any[]) => any; +} + +export const mockClear = (mock: MockProxy) => { + for (const key of Object.keys(mock)) { + if (mock[key] === null || mock[key] === undefined) { + continue; + } + + if (mock[key]._isMockObject) { + mockClear(mock[key]); + } + + if (mock[key]._isMockFunction) { + mock[key].mockClear(); + } + } + + // This is a catch for if they pass in a jest.fn() + if (!mock._isMockObject) { + return mock.mockClear(); + } +}; + +export const mockReset = (mock: MockProxy) => { + for (const key of Object.keys(mock)) { + if (mock[key] === null || mock[key] === undefined) { + continue; + } + + if (mock[key]._isMockObject) { + mockReset(mock[key]); + } + if (mock[key]._isMockFunction) { + mock[key].mockReset(); + } + } + + // This is a catch for if they pass in a jest.fn() + // Worst case, we will create a jest.fn() (since this is a proxy) + // below in the get and call mockReset on it + if (!mock._isMockObject) { + return mock.mockReset(); + } +}; + +export function mockDeep( + opts: { + funcPropSupport?: true; + fallbackMockImplementation?: MockOpts["fallbackMockImplementation"]; + }, + mockImplementation?: PartialDeep, +): DeepMockProxyWithFuncPropSupport; +export function mockDeep(mockImplementation?: PartialDeep): DeepMockProxy; +export function mockDeep(arg1: any, arg2?: any) { + const [opts, mockImplementation] = + typeof arg1 === "object" && + (typeof arg1.fallbackMockImplementation === "function" || arg1.funcPropSupport === true) + ? [arg1, arg2] + : [{}, arg1]; + return mock(mockImplementation, { + deep: true, + fallbackMockImplementation: opts.fallbackMockImplementation, + }); +} + +const overrideMockImp = (obj: PartialDeep, opts?: MockOpts) => { + const proxy = new Proxy>(obj, handler(opts)); + for (const name of Object.keys(obj)) { + if (typeof obj[name] === "object" && obj[name] !== null) { + proxy[name] = overrideMockImp(obj[name], opts); + } else { + proxy[name] = obj[name]; + } + } + + return proxy; +}; + +const handler = (opts?: MockOpts): ProxyHandler => ({ + ownKeys(target: MockProxy) { + return Reflect.ownKeys(target); + }, + + set: (obj: MockProxy, property: ProxiedProperty, value: any) => { + obj[property] = value; + return true; + }, + + get: (obj: MockProxy, property: ProxiedProperty) => { + const fn = calledWithFn({ fallbackMockImplementation: opts?.fallbackMockImplementation }); + + if (!(property in obj)) { + if (GLOBAL_CONFIG.ignoreProps?.includes(property)) { + return undefined; + } + // Jest's internal equality checking does some wierd stuff to check for iterable equality + if (property === Symbol.iterator) { + return obj[property]; + } + + if (property === "_deepMock") { + return obj[property]; + } + // So this calls check here is totally not ideal - jest internally does a + // check to see if this is a spy - which we want to say no to, but blindly returning + // an proxy for calls results in the spy check returning true. This is another reason + // why deep is opt in. + if (opts?.deep && property !== "calls") { + obj[property] = new Proxy>(fn, handler(opts)); + obj[property].replaceProperty = (value: T[K]) => { + obj[property] = value; + }; + obj[property].mockDeep = () => { + if (obj[property]._deepMock) { + return obj[property]._deepMock; + } + + const mock = mockDeep({ + fallbackMockImplementation: opts?.fallbackMockImplementation, + }); + (obj[property] as CalledWithMock).mockReturnValue(mock); + obj[property]._deepMock = mock; + return mock; + }; + obj[property]._isMockObject = true; + } else { + obj[property] = calledWithFn({ + fallbackMockImplementation: opts?.fallbackMockImplementation, + }); + } + } + + // @ts-expect-error Hack by author of jest-mock-extended + if (obj instanceof Date && typeof obj[property] === "function") { + // @ts-expect-error Hack by author of jest-mock-extended + return obj[property].bind(obj); + } + + return obj[property]; + }, +}); + +const mock = & T = MockProxy & T>( + mockImplementation: PartialDeep = {} as PartialDeep, + opts?: MockOpts, +): MockedReturn => { + // @ts-expect-error private + mockImplementation!._isMockObject = true; + return overrideMockImp(mockImplementation, opts); +}; + +export const mockFn = (): CalledWithMock & T => { + // @ts-expect-error Hack by author of jest-mock-extended + return calledWithFn(); +}; + +export const stub = (): T => { + return new Proxy({} as T, { + get: (obj, property: ProxiedProperty) => { + if (property in obj) { + // @ts-expect-error Hack by author of jest-mock-extended + return obj[property]; + } + return jest.fn(); + }, + }); +}; + +export default mock; diff --git a/libs/common/src/platform/spec/mock-sdk.service.ts b/libs/common/src/platform/spec/mock-sdk.service.ts new file mode 100644 index 00000000000..66a6ab3ec84 --- /dev/null +++ b/libs/common/src/platform/spec/mock-sdk.service.ts @@ -0,0 +1,81 @@ +import { + BehaviorSubject, + distinctUntilChanged, + map, + Observable, + takeWhile, + throwIfEmpty, +} from "rxjs"; + +import { BitwardenClient } from "@bitwarden/sdk-internal"; + +import { UserId } from "../../types/guid"; +import { SdkService, UserNotLoggedInError } from "../abstractions/sdk/sdk.service"; +import { Rc } from "../misc/reference-counting/rc"; + +import { DeepMockProxy, mockDeep } from "./mock-deep"; + +export class MockSdkService implements SdkService { + private userClients$ = new BehaviorSubject<{ + [userId: UserId]: Rc | undefined; + }>({}); + + private _client$ = new BehaviorSubject(mockDeep()); + client$ = this._client$.asObservable(); + + version$ = new BehaviorSubject("0.0.1-test").asObservable(); + + userClient$(userId: UserId): Observable> { + return this.userClients$.pipe( + takeWhile((clients) => clients[userId] !== undefined, false), + map((clients) => clients[userId] as Rc), + distinctUntilChanged(), + throwIfEmpty(() => new UserNotLoggedInError(userId)), + ); + } + + setClient(): void { + throw new Error("Not supported in mock service"); + } + + /** + * Returns the non-user scoped client mock. + * This is what is returned by the `client$` observable. + */ + get client(): DeepMockProxy { + return this._client$.value; + } + + readonly simulate = { + /** + * Simulates a user login, and returns a user-scoped mock for the user. + * This will be return by the `userClient$` observable. + * + * @param userId The userId to simulate login for. + * @returns A user-scoped mock for the user. + */ + userLogin: (userId: UserId) => { + const client = mockDeep(); + this.userClients$.next({ + ...this.userClients$.getValue(), + [userId]: new Rc(client), + }); + return client; + }, + + /** + * Simulates a user logout, and disposes the user-scoped mock for the user. + * This will remove the user-scoped mock from the `userClient$` observable. + * + * @param userId The userId to simulate logout for. + */ + userLogout: (userId: UserId) => { + const clients = this.userClients$.value; + clients[userId]?.markForDisposal(); + this.userClients$.next({ + ...clients, + [userId]: undefined, + }); + }, + }; +} diff --git a/libs/common/src/vault/services/totp.service.spec.ts b/libs/common/src/vault/services/totp.service.spec.ts index c653b4ce1db..4aca262d537 100644 --- a/libs/common/src/vault/services/totp.service.spec.ts +++ b/libs/common/src/vault/services/totp.service.spec.ts @@ -1,38 +1,27 @@ -import { mock } from "jest-mock-extended"; -import { of, take } from "rxjs"; +import { take } from "rxjs"; -import { BitwardenClient, TotpResponse } from "@bitwarden/sdk-internal"; +import { TotpResponse } from "@bitwarden/sdk-internal"; -import { SdkService } from "../../platform/abstractions/sdk/sdk.service"; +import { MockSdkService } from "../../platform/spec/mock-sdk.service"; import { TotpService } from "./totp.service"; describe("TotpService", () => { - let totpService: TotpService; - let generateTotpMock: jest.Mock; - - const sdkService = mock(); + let totpService!: TotpService; + let sdkService!: MockSdkService; beforeEach(() => { - generateTotpMock = jest - .fn() - .mockReturnValueOnce({ + sdkService = new MockSdkService(); + sdkService.client.vault + .mockDeep() + .totp.mockDeep() + .generate_totp.mockReturnValueOnce({ code: "123456", period: 30, }) .mockReturnValueOnce({ code: "654321", period: 30 }) .mockReturnValueOnce({ code: "567892", period: 30 }); - const mockBitwardenClient = { - vault: () => ({ - totp: () => ({ - generate_totp: generateTotpMock, - }), - }), - }; - - sdkService.client$ = of(mockBitwardenClient as unknown as BitwardenClient); - totpService = new TotpService(sdkService); // TOTP is time-based, so we need to mock the current time diff --git a/libs/importer/src/services/import.service.spec.ts b/libs/importer/src/services/import.service.spec.ts index 30309a3d9c2..6c8656f4c1d 100644 --- a/libs/importer/src/services/import.service.spec.ts +++ b/libs/importer/src/services/import.service.spec.ts @@ -1,5 +1,4 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { of } from "rxjs"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports @@ -8,14 +7,13 @@ import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { MockSdkService } from "@bitwarden/common/platform/spec/mock-sdk.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { KeyService } from "@bitwarden/key-management"; -import { BitwardenClient } from "@bitwarden/sdk-internal"; import { BitwardenPasswordProtectedImporter } from "../importers/bitwarden/bitwarden-password-protected-importer"; import { Importer } from "../importers/importer"; @@ -35,7 +33,7 @@ describe("ImportService", () => { let encryptService: MockProxy; let pinService: MockProxy; let accountService: MockProxy; - let sdkService: MockProxy; + let sdkService: MockSdkService; beforeEach(() => { cipherService = mock(); @@ -46,9 +44,7 @@ describe("ImportService", () => { keyService = mock(); encryptService = mock(); pinService = mock(); - const mockClient = mock(); - sdkService = mock(); - sdkService.client$ = of(mockClient, mockClient, mockClient); + sdkService = new MockSdkService(); importService = new ImportService( cipherService, diff --git a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts index 35cef914588..84d1dd7ad72 100644 --- a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts +++ b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts @@ -5,17 +5,17 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; +import { MockSdkService } from "@bitwarden/common/platform/spec/mock-sdk.service"; import { makeStaticByteArray, mockEnc } from "@bitwarden/common/spec"; import { CsprngArray } from "@bitwarden/common/types/csprng"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; -import { BitwardenClient, VerifyAsymmetricKeysResponse } from "@bitwarden/sdk-internal"; +import { VerifyAsymmetricKeysResponse } from "@bitwarden/sdk-internal"; import { KeyService } from "../../abstractions/key.service"; import { UserAsymmetricKeysRegenerationApiService } from "../abstractions/user-asymmetric-key-regeneration-api.service"; @@ -24,24 +24,17 @@ import { DefaultUserAsymmetricKeysRegenerationService } from "./default-user-asy function setupVerificationResponse( mockVerificationResponse: VerifyAsymmetricKeysResponse, - sdkService: MockProxy, + sdkService: MockSdkService, ) { const mockKeyPairResponse = { userPublicKey: "userPublicKey", userKeyEncryptedPrivateKey: "userKeyEncryptedPrivateKey", }; - sdkService.client$ = of({ - crypto: () => ({ - verify_asymmetric_keys: jest.fn().mockReturnValue(mockVerificationResponse), - make_key_pair: jest.fn().mockReturnValue(mockKeyPairResponse), - }), - free: jest.fn(), - echo: jest.fn(), - version: jest.fn(), - throw: jest.fn(), - catch: jest.fn(), - } as unknown as BitwardenClient); + sdkService.client.crypto + .mockDeep() + .verify_asymmetric_keys.mockReturnValue(mockVerificationResponse); + sdkService.client.crypto.mockDeep().make_key_pair.mockReturnValue(mockKeyPairResponse); } function setupUserKeyValidation( @@ -74,7 +67,7 @@ describe("regenerateIfNeeded", () => { let cipherService: MockProxy; let userAsymmetricKeysRegenerationApiService: MockProxy; let logService: MockProxy; - let sdkService: MockProxy; + let sdkService: MockSdkService; let apiService: MockProxy; let configService: MockProxy; let encryptService: MockProxy; @@ -84,7 +77,7 @@ describe("regenerateIfNeeded", () => { cipherService = mock(); userAsymmetricKeysRegenerationApiService = mock(); logService = mock(); - sdkService = mock(); + sdkService = new MockSdkService(); apiService = mock(); configService = mock(); encryptService = mock(); From 4bf1a3b670961eedbcf6f86dba2ac9e280ace708 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 07:16:46 -0700 Subject: [PATCH 174/245] [deps] Platform: Update Rust crate bytes to v1.10.1 (#14922) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 4 ++-- apps/desktop/desktop_native/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 34819a3981b..28d64e4f504 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -498,9 +498,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "camino" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index c4a2ed98e70..1fce5a7c597 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -18,7 +18,7 @@ base64 = "=0.22.1" bindgen = "=0.71.1" bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "3d48f140fd506412d186203238993163a8c4e536" } byteorder = "=1.5.0" -bytes = "=1.9.0" +bytes = "=1.10.1" cbc = "=0.1.2" core-foundation = "=0.10.0" dirs = "=6.0.0" From 169fdd5883352acae4fcc976d4122da5e9ea0a29 Mon Sep 17 00:00:00 2001 From: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Date: Wed, 28 May 2025 10:56:44 -0400 Subject: [PATCH 175/245] [PM-20650] Feature flag addition to clients (#14824) * Feature flag addition to clients * Updating feature flag name --- libs/common/src/enums/feature-flag.enum.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 8cd44f9d627..43b36c5692f 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -59,6 +59,7 @@ export enum FeatureFlag { CipherKeyEncryption = "cipher-key-encryption", PM18520_UpdateDesktopCipherForm = "pm-18520-desktop-cipher-forms", EndUserNotifications = "pm-10609-end-user-notifications", + RemoveCardItemTypePolicy = "pm-16442-remove-card-item-type-policy", /* Platform */ IpcChannelFramework = "ipc-channel-framework", @@ -107,6 +108,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.PM18520_UpdateDesktopCipherForm]: FALSE, [FeatureFlag.EndUserNotifications]: FALSE, [FeatureFlag.PM19941MigrateCipherDomainToSdk]: FALSE, + [FeatureFlag.RemoveCardItemTypePolicy]: FALSE, /* Auth */ [FeatureFlag.PM16117_ChangeExistingPasswordRefactor]: FALSE, From 6351fc0e69e46e91da8182bed7be1b15e66d19c4 Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Wed, 28 May 2025 13:36:46 -0700 Subject: [PATCH 176/245] fix(tailwind): [Auth/PM-22140] Use Tailwind for Password Settings header (#14978) `PM16117_ChangeExistingPasswordRefactor` flag ON --- .../password-settings/password-settings.component.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.html b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.html index 94cf08b5871..fc6620762f9 100644 --- a/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.html +++ b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.html @@ -1,6 +1,4 @@ -
-

{{ "changeMasterPassword" | i18n }}

-
+

{{ "changeMasterPassword" | i18n }}

{{ "loggedOutWarning" | i18n }} From 798acc7cba58fed70e55190534d60be14653d333 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 29 May 2025 15:17:04 +0200 Subject: [PATCH 177/245] [PM-21884] Fix DuckDuckGo integration when SDK is enabled for decrypt (#14884) * Fix ddg integration when sdk is enabled for decryption * Fix comments --- .../duckduckgo-message-handler.service.ts | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/services/duckduckgo-message-handler.service.ts b/apps/desktop/src/services/duckduckgo-message-handler.service.ts index 6fb91231be1..7bddaba499c 100644 --- a/apps/desktop/src/services/duckduckgo-message-handler.service.ts +++ b/apps/desktop/src/services/duckduckgo-message-handler.service.ts @@ -188,13 +188,10 @@ export class DuckDuckGoMessageHandlerService { } try { - let decryptedResult = await this.encryptService.decryptString( + const decryptedResult = await this.decryptDuckDuckGoEncString( message.encryptedCommand as EncString, this.duckduckgoSharedSecret, ); - - decryptedResult = this.trimNullCharsFromMessage(decryptedResult); - return JSON.parse(decryptedResult); } catch { this.sendResponse({ @@ -237,7 +234,46 @@ export class DuckDuckGoMessageHandlerService { ipc.platform.nativeMessaging.sendReply(response); } - // Trim all null bytes padded at the end of messages. This happens with C encryption libraries. + /* + * Bitwarden type 2 (AES256-CBC-HMAC256) uses PKCS7 padding. + * DuckDuckGo does not use PKCS7 padding; and instead fills the last CBC block with null bytes. + * ref: https://github.com/duckduckgo/apple-browsers/blob/04d678b447869c3a640714718a466b36407db8b6/macOS/DuckDuckGo/PasswordManager/Bitwarden/Services/BWEncryption.m#L141 + * + * This is incompatible which means the default encryptService cannot be used to decrypt the message, + * a custom EncString decrypt operation is needed. + * + * This function also trims null characters that are a result of the null-padding from the end of the message. + */ + private async decryptDuckDuckGoEncString( + encString: EncString, + key: SymmetricCryptoKey, + ): Promise { + const fastParams = this.cryptoFunctionService.aesDecryptFastParameters( + encString.data, + encString.iv, + encString.mac, + key, + ); + + const computedMac = await this.cryptoFunctionService.hmacFast( + fastParams.macData, + fastParams.macKey, + "sha256", + ); + const macsEqual = await this.cryptoFunctionService.compareFast(fastParams.mac, computedMac); + if (!macsEqual) { + return null; + } + const decryptedPaddedString = await this.cryptoFunctionService.aesDecryptFast({ + mode: "cbc", + parameters: fastParams, + }); + return this.trimNullCharsFromMessage(decryptedPaddedString); + } + + // DuckDuckGo does not use PKCS7 padding, but instead leaves the values as null, + // so null characters need to be trimmed from the end of the message for the last + // CBC-block. private trimNullCharsFromMessage(message: string): string { const charNull = 0; const charRightCurlyBrace = 125; From b48356228c5ca1437f5ede48f34aa0c79e877d79 Mon Sep 17 00:00:00 2001 From: Leslie Tilton <23057410+Banrion@users.noreply.github.com> Date: Thu, 29 May 2025 08:45:40 -0500 Subject: [PATCH 178/245] Update risk insights report to default an invalid uri to the original uri (#14800) --- .../reports/risk-insights/services/ciphers.mock.ts | 3 +++ .../services/risk-insights-report.service.spec.ts | 14 +++++++++----- .../services/risk-insights-report.service.ts | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/ciphers.mock.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/ciphers.mock.ts index ca5cdc35b8a..f697d24f208 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/ciphers.mock.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/ciphers.mock.ts @@ -27,6 +27,9 @@ export const mockCiphers: any[] = [ createLoginUriView("accounts.google.com"), createLoginUriView("https://www.google.com"), createLoginUriView("https://www.google.com/login"), + createLoginUriView("www.invalid@uri@.com"), + createLoginUriView("www.invaliduri!.com"), + createLoginUriView("this_is-not|a-valid-uri123@+"), ], }, edit: false, diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts index f9177bf1bf7..3aa624f1e59 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts @@ -50,7 +50,7 @@ describe("RiskInsightsReportService", () => { let testCase = testCaseResults[0]; expect(testCase).toBeTruthy(); expect(testCase.cipherMembers).toHaveLength(2); - expect(testCase.trimmedUris).toHaveLength(2); + expect(testCase.trimmedUris).toHaveLength(5); expect(testCase.weakPasswordDetail).toBeTruthy(); expect(testCase.exposedPasswordDetail).toBeTruthy(); expect(testCase.reusedPasswordCount).toEqual(2); @@ -69,12 +69,16 @@ describe("RiskInsightsReportService", () => { it("should generate the raw data + uri report correctly", async () => { const result = await firstValueFrom(service.generateRawDataUriReport$("orgId")); - expect(result).toHaveLength(8); + expect(result).toHaveLength(11); // Two ciphers that have google.com as their uri. There should be 2 results const googleResults = result.filter((x) => x.trimmedUri === "google.com"); expect(googleResults).toHaveLength(2); + // There is an invalid uri and it should not be trimmed + const invalidUriResults = result.filter((x) => x.trimmedUri === "this_is-not|a-valid-uri123@+"); + expect(invalidUriResults).toHaveLength(1); + // Verify the details for one of the googles matches the password health info // expected const firstGoogle = googleResults.filter( @@ -88,7 +92,7 @@ describe("RiskInsightsReportService", () => { it("should generate applications health report data correctly", async () => { const result = await firstValueFrom(service.generateApplicationsReport$("orgId")); - expect(result).toHaveLength(5); + expect(result).toHaveLength(8); // Two ciphers have google.com associated with them. The first cipher // has 2 members and the second has 4. However, the 2 members in the first @@ -132,7 +136,7 @@ describe("RiskInsightsReportService", () => { expect(reportSummary.totalMemberCount).toEqual(7); expect(reportSummary.totalAtRiskMemberCount).toEqual(6); - expect(reportSummary.totalApplicationCount).toEqual(5); - expect(reportSummary.totalAtRiskApplicationCount).toEqual(4); + expect(reportSummary.totalApplicationCount).toEqual(8); + expect(reportSummary.totalAtRiskApplicationCount).toEqual(7); }); }); diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts index e4fece801b6..6fdab58115d 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts @@ -433,7 +433,7 @@ export class RiskInsightsReportService { const cipherUris: string[] = []; const uris = cipher.login?.uris ?? []; uris.map((u: { uri: string }) => { - const uri = Utils.getDomain(u.uri); + const uri = Utils.getDomain(u.uri) ?? u.uri; if (!cipherUris.includes(uri)) { cipherUris.push(uri); } From c48e4be14b546d53bf77d837df6d7b0bc737ead6 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 29 May 2025 16:05:28 +0200 Subject: [PATCH 179/245] Pin @types/lowdb to v1 (#14957) --- .github/renovate.json5 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index f30bc06e4a2..453e5e29c44 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -413,6 +413,12 @@ allowedVersions: "1.0.0", description: "Higher versions of lowdb are not compatible with CommonJS", }, + { + // Pin types as well since we are not upgrading past v1 (and also v2+ does not need separate types). + matchPackageNames: ["@types/lowdb"], + allowedVersions: "< 2.0.0", + description: "Higher versions of lowdb do not need separate types", + }, ], ignoreDeps: ["@types/koa-bodyparser", "bootstrap", "node-ipc", "@bitwarden/sdk-internal"], } From 0715597e8e059e22e232df376054a01baf6610c5 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Thu, 29 May 2025 15:06:07 +0100 Subject: [PATCH 180/245] [PM-21603]Invite Member sub text seat count does not account for sponsorships (#14954) * Resolve the membership count * Get the occupied Seat count from metadata --- .../admin-console/organizations/members/members.component.ts | 5 ++++- .../response/organization-billing-metadata.response.ts | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index e5a94bc4b4f..4f453762b5d 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -110,8 +110,10 @@ export class MembersComponent extends BaseMembersComponent protected rowHeight = 69; protected rowHeightClass = `tw-h-[69px]`; + private organizationUsersCount = 0; + get occupiedSeatCount(): number { - return this.dataSource.activeUserCount; + return this.organizationUsersCount; } constructor( @@ -218,6 +220,7 @@ export class MembersComponent extends BaseMembersComponent ); this.orgIsOnSecretsManagerStandalone = billingMetadata.isOnSecretsManagerStandalone; + this.organizationUsersCount = billingMetadata.organizationOccupiedSeats; await this.load(); diff --git a/libs/common/src/billing/models/response/organization-billing-metadata.response.ts b/libs/common/src/billing/models/response/organization-billing-metadata.response.ts index d30ad76a147..aa34c37bd1d 100644 --- a/libs/common/src/billing/models/response/organization-billing-metadata.response.ts +++ b/libs/common/src/billing/models/response/organization-billing-metadata.response.ts @@ -11,6 +11,7 @@ export class OrganizationBillingMetadataResponse extends BaseResponse { invoiceCreatedDate: Date | null; subPeriodEndDate: Date | null; isSubscriptionCanceled: boolean; + organizationOccupiedSeats: number; constructor(response: any) { super(response); @@ -25,6 +26,7 @@ export class OrganizationBillingMetadataResponse extends BaseResponse { this.invoiceCreatedDate = this.parseDate(this.getResponseProperty("InvoiceCreatedDate")); this.subPeriodEndDate = this.parseDate(this.getResponseProperty("SubPeriodEndDate")); this.isSubscriptionCanceled = this.getResponseProperty("IsSubscriptionCanceled"); + this.organizationOccupiedSeats = this.getResponseProperty("OrganizationOccupiedSeats"); } private parseDate(dateString: any): Date | null { From 058eb9a04be3b7d422694bfb4519a84d477aad25 Mon Sep 17 00:00:00 2001 From: Jared McCannon Date: Thu, 29 May 2025 11:17:30 -0400 Subject: [PATCH 181/245] [PM-19127] - Nested Traverse Optimization (#14881) * Draft optimization of getNestedCollectionTree * Added feature flag to wrap nestedTraverse_vNext. added the old implementation back in for feature flagging. * Correction from CR * Copied tests over for the vNext method. --------- Co-authored-by: Thomas Rittson --- .../collections/utils/collection-utils.ts | 25 ++++++ .../collections/vault.component.ts | 19 ++++- .../vault/individual-vault/vault.component.ts | 15 +++- libs/common/src/enums/feature-flag.enum.ts | 2 + libs/common/src/vault/service-utils.spec.ts | 18 +++++ libs/common/src/vault/service-utils.ts | 81 ++++++++++++++++--- 6 files changed, 141 insertions(+), 19 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts index 95ae911bbf6..f19c3f64530 100644 --- a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts +++ b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts @@ -37,6 +37,31 @@ export function getNestedCollectionTree( return nodes; } +export function getNestedCollectionTree_vNext( + collections: (CollectionView | CollectionAdminView)[], +): TreeNode[] { + if (!collections) { + return []; + } + + // Collections need to be cloned because ServiceUtils.nestedTraverse actively + // modifies the names of collections. + // These changes risk affecting collections store in StateService. + const clonedCollections = collections + .sort((a, b) => a.name.localeCompare(b.name)) + .map(cloneCollection); + + const nodes: TreeNode[] = []; + clonedCollections.forEach((collection) => { + const parts = + collection.name != null + ? collection.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) + : []; + ServiceUtils.nestedTraverse_vNext(nodes, 0, parts, collection, null, NestingDelimiter); + }); + return nodes; +} + export function getFlatCollectionTree( nodes: TreeNode[], ): CollectionAdminView[]; diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts index a3b62838d6a..19373f193d9 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts @@ -125,7 +125,11 @@ import { BulkCollectionsDialogResult, } from "./bulk-collections-dialog"; import { CollectionAccessRestrictedComponent } from "./collection-access-restricted.component"; -import { getNestedCollectionTree, getFlatCollectionTree } from "./utils"; +import { + getNestedCollectionTree, + getFlatCollectionTree, + getNestedCollectionTree_vNext, +} from "./utils"; import { VaultFilterModule } from "./vault-filter/vault-filter.module"; import { VaultHeaderComponent } from "./vault-header/vault-header.component"; @@ -420,9 +424,16 @@ export class VaultComponent implements OnInit, OnDestroy { }), ); - const nestedCollections$ = allCollections$.pipe( - map((collections) => getNestedCollectionTree(collections)), - shareReplay({ refCount: true, bufferSize: 1 }), + const nestedCollections$ = combineLatest([ + this.allCollectionsWithoutUnassigned$, + this.configService.getFeatureFlag$(FeatureFlag.OptimizeNestedTraverseTypescript), + ]).pipe( + map( + ([collections, shouldOptimize]) => + (shouldOptimize + ? getNestedCollectionTree_vNext(collections) + : getNestedCollectionTree(collections)) as TreeNode[], + ), ); const collections$ = combineLatest([ diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 6e751f600dc..0dfaa1ac589 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -49,7 +49,9 @@ import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { EventType } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -82,6 +84,7 @@ import { import { getNestedCollectionTree, getFlatCollectionTree, + getNestedCollectionTree_vNext, } from "../../admin-console/organizations/collections"; import { CollectionDialogAction, @@ -270,6 +273,7 @@ export class VaultComponent implements OnInit, OnDestroy { private trialFlowService: TrialFlowService, private organizationBillingService: OrganizationBillingServiceAbstraction, private billingNotificationService: BillingNotificationService, + private configService: ConfigService, ) {} async ngOnInit() { @@ -326,8 +330,15 @@ export class VaultComponent implements OnInit, OnDestroy { const filter$ = this.routedVaultFilterService.filter$; const allCollections$ = this.collectionService.decryptedCollections$; - const nestedCollections$ = allCollections$.pipe( - map((collections) => getNestedCollectionTree(collections)), + const nestedCollections$ = combineLatest([ + allCollections$, + this.configService.getFeatureFlag$(FeatureFlag.OptimizeNestedTraverseTypescript), + ]).pipe( + map(([collections, shouldOptimize]) => + shouldOptimize + ? getNestedCollectionTree_vNext(collections) + : getNestedCollectionTree(collections), + ), ); this.searchText$ diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 43b36c5692f..696f7028159 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -13,6 +13,7 @@ export enum FeatureFlag { /* Admin Console Team */ LimitItemDeletion = "pm-15493-restrict-item-deletion-to-can-manage-permission", SeparateCustomRolePermissions = "pm-19917-separate-custom-role-permissions", + OptimizeNestedTraverseTypescript = "pm-21695-optimize-nested-traverse-typescript", /* Auth */ PM16117_ChangeExistingPasswordRefactor = "pm-16117-change-existing-password-refactor", @@ -82,6 +83,7 @@ export const DefaultFeatureFlagValue = { /* Admin Console Team */ [FeatureFlag.LimitItemDeletion]: FALSE, [FeatureFlag.SeparateCustomRolePermissions]: FALSE, + [FeatureFlag.OptimizeNestedTraverseTypescript]: FALSE, /* Autofill */ [FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE, diff --git a/libs/common/src/vault/service-utils.spec.ts b/libs/common/src/vault/service-utils.spec.ts index db414da76d7..619d3d72ee6 100644 --- a/libs/common/src/vault/service-utils.spec.ts +++ b/libs/common/src/vault/service-utils.spec.ts @@ -36,6 +36,24 @@ describe("serviceUtils", () => { }); }); + describe("nestedTraverse_vNext", () => { + it("should traverse a tree and add a node at the correct position given a valid path", () => { + const nodeToBeAdded: FakeObject = { id: "1.2.1", name: "1.2.1" }; + const path = ["1", "1.2", "1.2.1"]; + + ServiceUtils.nestedTraverse_vNext(nodeTree, 0, path, nodeToBeAdded, null, "/"); + expect(nodeTree[0].children[1].children[0].node).toEqual(nodeToBeAdded); + }); + + it("should combine the path for missing nodes and use as the added node name given an invalid path", () => { + const nodeToBeAdded: FakeObject = { id: "blank", name: "blank" }; + const path = ["3", "3.1", "3.1.1"]; + + ServiceUtils.nestedTraverse_vNext(nodeTree, 0, path, nodeToBeAdded, null, "/"); + expect(nodeTree[2].children[0].node.name).toEqual("3.1/3.1.1"); + }); + }); + describe("getTreeNodeObject", () => { it("should return a matching node given a single tree branch and a valid id", () => { const id = "1.1.1"; diff --git a/libs/common/src/vault/service-utils.ts b/libs/common/src/vault/service-utils.ts index 5fbc550d6af..96ae406fae4 100644 --- a/libs/common/src/vault/service-utils.ts +++ b/libs/common/src/vault/service-utils.ts @@ -3,15 +3,6 @@ import { ITreeNodeObject, TreeNode } from "./models/domain/tree-node"; export class ServiceUtils { - /** - * Recursively adds a node to nodeTree - * @param {TreeNode[]} nodeTree - An array of TreeNodes that the node will be added to - * @param {number} partIndex - Index of the `parts` array that is being processed - * @param {string[]} parts - Array of strings that represent the path to the `obj` node - * @param {ITreeNodeObject} obj - The node to be added to the tree - * @param {ITreeNodeObject} parent - The parent node of the `obj` node - * @param {string} delimiter - The delimiter used to split the path string, will be used to combine the path for missing nodes - */ static nestedTraverse( nodeTree: TreeNode[], partIndex: number, @@ -70,11 +61,75 @@ export class ServiceUtils { } } + /** + * Recursively adds a node to nodeTree + * @param {TreeNode[]} nodeTree - An array of TreeNodes that the node will be added to + * @param {number} partIndex - Index of the `parts` array that is being processed + * @param {string[]} parts - Array of strings that represent the path to the `obj` node + * @param {ITreeNodeObject} obj - The node to be added to the tree + * @param {ITreeNodeObject} parent - The parent node of the `obj` node + * @param {string} delimiter - The delimiter used to split the path string, will be used to combine the path for missing nodes + */ + static nestedTraverse_vNext( + nodeTree: TreeNode[], + partIndex: number, + parts: string[], + obj: ITreeNodeObject, + parent: TreeNode | undefined, + delimiter: string, + ) { + if (parts.length <= partIndex) { + return; + } + + // 'end' indicates we've traversed as far as we can based on the object name + const end: boolean = partIndex === parts.length - 1; + const partName: string = parts[partIndex]; + + // If we're at the end, just add the node - it doesn't matter what else is here + if (end) { + nodeTree.push(new TreeNode(obj, parent, partName)); + return; + } + + // Get matching nodes at this level by name + // NOTE: this is effectively a loop so we only want to do it once + const matchingNodes = nodeTree.filter((n) => n.node.name === partName); + + // If there are no matching nodes... + if (matchingNodes.length === 0) { + // And we're not at the end of the path (because we didn't trigger the early return above), + // combine the current name with the next name. + // 1, *1.2, 1.2.1 becomes + // 1, *1.2/1.2.1 + const newPartName = partName + delimiter + parts[partIndex + 1]; + ServiceUtils.nestedTraverse_vNext( + nodeTree, + 0, + [newPartName, ...parts.slice(partIndex + 2)], + obj, + parent, + delimiter, + ); + } else { + // There is a node here with the same name, descend into it + ServiceUtils.nestedTraverse_vNext( + matchingNodes[0].children, + partIndex + 1, + parts, + obj, + matchingNodes[0], + delimiter, + ); + return; + } + } + /** * Searches a tree for a node with a matching `id` - * @param {TreeNode} nodeTree - A single TreeNode branch that will be searched + * @param {TreeNode} nodeTree - A single TreeNode branch that will be searched * @param {string} id - The id of the node to be found - * @returns {TreeNode} The node with a matching `id` + * @returns {TreeNode} The node with a matching `id` */ static getTreeNodeObject( nodeTree: TreeNode, @@ -96,9 +151,9 @@ export class ServiceUtils { /** * Searches an array of tree nodes for a node with a matching `id` - * @param {TreeNode} nodeTree - An array of TreeNode branches that will be searched + * @param {TreeNode} nodeTree - An array of TreeNode branches that will be searched * @param {string} id - The id of the node to be found - * @returns {TreeNode} The node with a matching `id` + * @returns {TreeNode} The node with a matching `id` */ static getTreeNodeObjectFromList( nodeTree: TreeNode[], From 8966b4fb50c7dff9a02cfbb22f9cfa8b26ee1553 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 29 May 2025 20:48:03 +0200 Subject: [PATCH 182/245] Fix flatpak autostart disabling (#14920) --- apps/desktop/src/main/messaging.main.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/main/messaging.main.ts b/apps/desktop/src/main/messaging.main.ts index 556fa293108..bc8d9ae4685 100644 --- a/apps/desktop/src/main/messaging.main.ts +++ b/apps/desktop/src/main/messaging.main.ts @@ -10,7 +10,7 @@ import { autostart } from "@bitwarden/desktop-napi"; import { Main } from "../main"; import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; -import { isFlatpak } from "../utils"; +import { isFlatpak, isLinux, isSnapStore } from "../utils"; import { MenuUpdateRequest } from "./menu/menu.updater"; @@ -26,8 +26,11 @@ export class MessagingMain { async init() { this.scheduleNextSync(); - if (process.platform === "linux") { - await this.desktopSettingsService.setOpenAtLogin(fs.existsSync(this.linuxStartupFile())); + if (isLinux()) { + // Flatpak and snap don't have access to or use the startup file. On flatpak, the autostart portal is used + if (!isFlatpak() && !isSnapStore()) { + await this.desktopSettingsService.setOpenAtLogin(fs.existsSync(this.linuxStartupFile())); + } } else { const loginSettings = app.getLoginItemSettings(); await this.desktopSettingsService.setOpenAtLogin(loginSettings.openAtLogin); From bb9006e6e4b0260b1552ac230b86420bf3696504 Mon Sep 17 00:00:00 2001 From: Github Actions Date: Thu, 29 May 2025 19:15:34 +0000 Subject: [PATCH 183/245] Bumped Desktop client to 2025.5.1 --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 21892cd1df8..64f2b188d72 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2025.5.0", + "version": "2025.5.1", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index b3a33dc75e3..7b48c4af1d5 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.5.0", + "version": "2025.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.5.0", + "version": "2025.5.1", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index c180ed8c744..e2bc869f9f3 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2025.5.0", + "version": "2025.5.1", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index 691705cc280..071f42c94c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -232,7 +232,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2025.5.0", + "version": "2025.5.1", "hasInstallScript": true, "license": "GPL-3.0" }, From eed288d79731393b753f7ee638dabb645d62fba0 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Thu, 29 May 2025 13:01:07 -0700 Subject: [PATCH 184/245] [PM-21724] - add safari and firefox to list of potential browser vendors (#14857) * add safari and firefox to list of potential browser vendors * use browserClientVendorExtended * handle unknown browser client vendor --- .../popup/settings/autofill.component.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.ts b/apps/browser/src/autofill/popup/settings/autofill.component.ts index e1a5a2fc218..8c5c8e600a0 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill.component.ts @@ -334,11 +334,24 @@ export class AutofillComponent implements OnInit { return null; } + get browserClientVendorExtended() { + if (this.browserClientVendor !== BrowserClientVendors.Unknown) { + return this.browserClientVendor; + } + if (this.platformUtilsService.isFirefox()) { + return "Firefox"; + } + if (this.platformUtilsService.isSafari()) { + return "Safari"; + } + return BrowserClientVendors.Unknown; + } + get spotlightButtonText() { - if (this.browserClientVendor === BrowserClientVendors.Unknown) { + if (this.browserClientVendorExtended === BrowserClientVendors.Unknown) { return this.i18nService.t("turnOffAutofill"); } - return this.i18nService.t("turnOffBrowserAutofill", this.browserClientVendor); + return this.i18nService.t("turnOffBrowserAutofill", this.browserClientVendorExtended); } async dismissSpotlight() { From 21dfcfeadaaf61462aed1cd7dcc45cdf58015d58 Mon Sep 17 00:00:00 2001 From: aj-bw <81774843+aj-bw@users.noreply.github.com> Date: Thu, 29 May 2025 17:18:59 -0400 Subject: [PATCH 185/245] fix chromatic linter failure (#14972) --- .github/workflows/chromatic.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 47f3b310504..78733bc5a8b 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -7,7 +7,9 @@ on: - "rc" - "hotfix-rc" pull_request_target: - types: [opened, synchronize] + types: [opened, synchronize, reopened] + branches: + - "main" jobs: check-run: From 949e9b14ab6bacd5c18b06b4ad8b32ba4a655918 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 09:14:36 +0200 Subject: [PATCH 186/245] Autosync the updated translations (#14997) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 3 + apps/browser/src/_locales/az/messages.json | 3 + apps/browser/src/_locales/be/messages.json | 3 + apps/browser/src/_locales/bg/messages.json | 3 + apps/browser/src/_locales/bn/messages.json | 3 + apps/browser/src/_locales/bs/messages.json | 3 + apps/browser/src/_locales/ca/messages.json | 11 +- apps/browser/src/_locales/cs/messages.json | 3 + apps/browser/src/_locales/cy/messages.json | 3 + apps/browser/src/_locales/da/messages.json | 3 + apps/browser/src/_locales/de/messages.json | 11 +- apps/browser/src/_locales/el/messages.json | 3 + apps/browser/src/_locales/en_GB/messages.json | 3 + apps/browser/src/_locales/en_IN/messages.json | 3 + apps/browser/src/_locales/es/messages.json | 5 +- apps/browser/src/_locales/et/messages.json | 3 + apps/browser/src/_locales/eu/messages.json | 3 + apps/browser/src/_locales/fa/messages.json | 3 + apps/browser/src/_locales/fi/messages.json | 5 +- apps/browser/src/_locales/fil/messages.json | 3 + apps/browser/src/_locales/fr/messages.json | 25 +-- apps/browser/src/_locales/gl/messages.json | 3 + apps/browser/src/_locales/he/messages.json | 3 + apps/browser/src/_locales/hi/messages.json | 3 + apps/browser/src/_locales/hr/messages.json | 3 + apps/browser/src/_locales/hu/messages.json | 3 + apps/browser/src/_locales/id/messages.json | 3 + apps/browser/src/_locales/it/messages.json | 113 ++++++------- apps/browser/src/_locales/ja/messages.json | 3 + apps/browser/src/_locales/ka/messages.json | 3 + apps/browser/src/_locales/km/messages.json | 3 + apps/browser/src/_locales/kn/messages.json | 3 + apps/browser/src/_locales/ko/messages.json | 3 + apps/browser/src/_locales/lt/messages.json | 3 + apps/browser/src/_locales/lv/messages.json | 5 +- apps/browser/src/_locales/ml/messages.json | 3 + apps/browser/src/_locales/mr/messages.json | 3 + apps/browser/src/_locales/my/messages.json | 3 + apps/browser/src/_locales/nb/messages.json | 3 + apps/browser/src/_locales/ne/messages.json | 3 + apps/browser/src/_locales/nl/messages.json | 5 +- apps/browser/src/_locales/nn/messages.json | 3 + apps/browser/src/_locales/or/messages.json | 3 + apps/browser/src/_locales/pl/messages.json | 3 + apps/browser/src/_locales/pt_BR/messages.json | 3 + apps/browser/src/_locales/pt_PT/messages.json | 3 + apps/browser/src/_locales/ro/messages.json | 3 + apps/browser/src/_locales/ru/messages.json | 3 + apps/browser/src/_locales/si/messages.json | 3 + apps/browser/src/_locales/sk/messages.json | 7 +- apps/browser/src/_locales/sl/messages.json | 3 + apps/browser/src/_locales/sr/messages.json | 21 +-- apps/browser/src/_locales/sv/messages.json | 153 +++++++++--------- apps/browser/src/_locales/te/messages.json | 3 + apps/browser/src/_locales/th/messages.json | 3 + apps/browser/src/_locales/tr/messages.json | 3 + apps/browser/src/_locales/uk/messages.json | 47 +++--- apps/browser/src/_locales/vi/messages.json | 3 + apps/browser/src/_locales/zh_CN/messages.json | 15 +- apps/browser/src/_locales/zh_TW/messages.json | 3 + 60 files changed, 372 insertions(+), 192 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 48ae14fc1ce..426dba6ae2c 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "استخدم كلمة المرور هذه" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "استخدم اسم المستخدم هذا" }, diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index c9096bce0d6..03a2aa103c6 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Bu parolu istifadə et" }, + "useThisPassphrase": { + "message": "Bu keçid ifadəsini istifadə et" + }, "useThisUsername": { "message": "Bu istifadəçi adını istifadə et" }, diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 0dd5ff4163f..4a6c11763af 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Выкарыстоўваць гэты пароль" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Выкарыстоўваць гэта імя карыстальніка" }, diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 0bf8dbce2b0..b6bb159b354 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Използване на тази парола" }, + "useThisPassphrase": { + "message": "Използване на тази парола-фраза" + }, "useThisUsername": { "message": "Използване на това потребителско име" }, diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index f7f28115faa..f58a878f83c 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 1dc35addd8e..0f064076440 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 9517257ac3a..3ea0c595916 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -1621,10 +1621,10 @@ "message": "Mostra suggeriments d'emplenament automàtic als camps del formulari" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "Mostra identitats com a suggeriments" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "Mostra targetes com a suggeriments" }, "showInlineMenuOnIconSelectionLabel": { "message": "Mostra suggeriments quan la icona està seleccionada" @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Utilitzeu aquesta contrasenya" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Utilitzeu aquest nom d'usuari" }, @@ -2219,7 +2222,7 @@ "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "vaultCustomization": { - "message": "Vault customization" + "message": "Personalització de la caixa forta" }, "vaultTimeoutAction": { "message": "Acció quan acabe el temps d'espera de la caixa forta" @@ -5253,7 +5256,7 @@ "message": "Change at-risk password" }, "settingsVaultOptions": { - "message": "Vault options" + "message": "Opcions de la caixa forta" }, "emptyVaultDescription": { "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index b213e0aee7d..354f37268de 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Použít toto heslo" }, + "useThisPassphrase": { + "message": "Použít tuto heslovou frázi" + }, "useThisUsername": { "message": "Použít toto uživatelské jméno" }, diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index ee74d1e45e2..b26cbf21019 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Defnyddio'r cyfrinair hwn" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Defnyddio'r enw defnyddiwr hwn" }, diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index da24e1bcfb9..b64655ab399 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Anvend denne adgangskode" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Anvend dette brugernavn" }, diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index f7303885551..34fecaadb72 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Dieses Passwort verwenden" }, + "useThisPassphrase": { + "message": "Diese Passphrase verwenden" + }, "useThisUsername": { "message": "Diesen Benutzernamen verwenden" }, @@ -3620,7 +3623,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsBodyNoItems": { - "message": "Teile Dateien und Daten sicher mit jedem auf jeder Plattform. Deine Informationen bleiben Ende-zu-Ende-Verschlüsselt, während die Verbreitung begrenzt wird.", + "message": "Teile Dateien und Daten sicher mit jedem auf jeder Plattform. Deine Informationen bleiben Ende-zu-Ende-verschlüsselt, während die Verbreitung begrenzt wird.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -5256,7 +5259,7 @@ "message": "Tresoroptionen" }, "emptyVaultDescription": { - "message": "Der Tresor schützt mehr als nur deine Passwörter. Speicher hier sicher Zugangsdaten, Ausweise, Karten und Notizen." + "message": "Der Tresor schützt mehr als nur deine Passwörter. Speicher hier sicher Zugangsdaten, Identitäten, Karten und Notizen." }, "introCarouselLabel": { "message": "Willkommen bei Bitwarden" @@ -5367,12 +5370,12 @@ "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyTwo": { - "message": ", um deine Zugangsdaten sicher aufzubewahren.", + "message": ", um dir zu helfen, deine Zugangsdaten sicher zu halten.", "description": "Two part message", "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyAria": { - "message": "Generiere ganz einfach starke und einzigartige Passwörter, indem du auf den \"Passwort generieren\"-Button klickst, um deine Zugangsdaten sicher aufzubewahren.", + "message": "Generiere ganz einfach starke und einzigartige Passwörter, indem du auf den \"Passwort generieren\"-Button klickst, um dir zu helfen, deine Zugangsdaten sicher zu halten.", "description": "Aria label for the body content of the generator nudge" }, "noPermissionsViewPage": { diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index ed81dfa744e..3d67010a32d 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Χρήση αυτού του κωδικού πρόσβασης" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Χρήση αυτού του ονόματος χρήστη" }, diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 5eecf989894..af82196d643 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index adb3cf6ec32..d0fc2af6f9b 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 94d02848b5f..536131b6b78 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -884,7 +884,7 @@ "message": "Se requiere inicio de sesión en dos pasos para tu cuenta. Sigue los pasos siguientes para terminar de iniciar sesión." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Follow the steps below to finish logging in." + "message": "Sigue los pasos de abajo para terminar de iniciar sesión." }, "followTheStepsBelowToFinishLoggingInWithSecurityKey": { "message": "Follow the steps below to finish logging in with your security key." @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Usar esta contraseña" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Usar este nombre de usuario" }, diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 26b4bf69fb4..1c09897c53b 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Kasuta seda parooli" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Kasuta seda kasutajanime" }, diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index b3f525d7be5..cb855a077bc 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 33a779a7909..190af9225a3 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "از این کلمه عبور استفاده کن" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "از این نام کاربری استفاده کن" }, diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 8e5eede202a..318b80997c9 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Käytä tätä salasanaa" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Käytä tätä käyttäjätunnusta" }, @@ -3018,7 +3021,7 @@ "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." }, "organizationName": { - "message": "Organization name" + "message": "Organisaation nimi" }, "keyConnectorDomain": { "message": "Key Connector domain" diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index e8e45249773..e4c2c746f80 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 736fd21349e..5ee6102b550 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -1121,11 +1121,11 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "Déverrouiller pour enregistrer l'identifiant", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { - "message": "Save login", + "message": "Enregistrer l'identifiant", "description": "Prompt asking the user if they want to save their login details." }, "updateLogin": { @@ -1615,7 +1615,7 @@ } }, "turnOffAutofill": { - "message": "Turn off autofill" + "message": "Désactiver la saisie automatique" }, "showInlineMenuLabel": { "message": "Afficher les suggestions de saisie automatique dans les champs d'un formulaire" @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Utiliser ce mot de passe" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Utiliser ce nom d'utilisateur" }, @@ -2374,7 +2377,7 @@ "message": "Politique de confidentialité" }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "Votre nouveau mot de passe ne peut être le même que votre mot de passe actuel." }, "hintEqualsPassword": { "message": "Votre indice de mot de passe ne peut pas être identique à votre mot de passe." @@ -3018,7 +3021,7 @@ "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." }, "organizationName": { - "message": "Organization name" + "message": "Nom de l'organisation" }, "keyConnectorDomain": { "message": "Key Connector domain" @@ -3587,10 +3590,10 @@ "message": "Trust organization" }, "trust": { - "message": "Trust" + "message": "Faire confiance" }, "doNotTrust": { - "message": "Do not trust" + "message": "Ne pas faire confiance" }, "organizationNotTrusted": { "message": "Organization is not trusted" @@ -4540,19 +4543,19 @@ } }, "downloadBitwarden": { - "message": "Download Bitwarden" + "message": "Télécharger Bitwarden" }, "downloadBitwardenOnAllDevices": { - "message": "Download Bitwarden on all devices" + "message": "Télécharger Bitwarden sur tous les appareils" }, "getTheMobileApp": { - "message": "Get the mobile app" + "message": "Télécharger l'application mobile" }, "getTheMobileAppDesc": { "message": "Access your passwords on the go with the Bitwarden mobile app." }, "getTheDesktopApp": { - "message": "Get the desktop app" + "message": "Télécharger l'application de bureau" }, "getTheDesktopAppDesc": { "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index e10287f3e7b..fbe708b1b08 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Usar este contrasinal" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Usar este nome de usuario" }, diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 1b264d9a70a..b8f5ec0b0b1 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "השתמש בסיסמה זו" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "השתמש בשם משתמש זה" }, diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 61c0fbe9963..a283bd6f438 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index ad32937c740..bac097fe112 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Koristi ovu lozinku" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Koristi ovo korisničko ime" }, diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 50607787cd7..0572fc77789 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Jelszó használata" }, + "useThisPassphrase": { + "message": "Jelmondat használata" + }, "useThisUsername": { "message": "Felhasználónév használata" }, diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index a12137d696a..7f8fea33a2e 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Gunakan kata sandi ini" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Gunakan nama pengguna ini" }, diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 9a5d2c65bb6..c733192493a 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -3,7 +3,7 @@ "message": "Bitwarden" }, "appLogoLabel": { - "message": "Bitwarden logo" + "message": "Logo di Bitwarden" }, "extName": { "message": "Bitwarden Password Manager", @@ -23,7 +23,7 @@ "message": "Crea account" }, "newToBitwarden": { - "message": "Sei un nuovo utente?" + "message": "Sei nuovo su Bitwarden?" }, "logInWithPasskey": { "message": "Accedi con passkey" @@ -32,7 +32,7 @@ "message": "Usa il Single Sign-On" }, "welcomeBack": { - "message": "Bentornat*" + "message": "Bentornato/a" }, "setAStrongPassword": { "message": "Imposta una password robusta" @@ -887,7 +887,7 @@ "message": "Segui i passaggi qui sotto per completare l'accesso." }, "followTheStepsBelowToFinishLoggingInWithSecurityKey": { - "message": "Follow the steps below to finish logging in with your security key." + "message": "Segui i passaggi seguenti per finire di accedere con la tua chiave di sicurezza." }, "restartRegistration": { "message": "Ricomincia la registrazione" @@ -1063,7 +1063,7 @@ "message": "Salva" }, "notificationViewAria": { - "message": "View $ITEMNAME$, opens in new window", + "message": "Visualizza $ITEMNAME$, si apre in una nuova finestra", "placeholders": { "itemName": { "content": "$1" @@ -1072,18 +1072,18 @@ "description": "Aria label for the view button in notification bar confirmation message" }, "notificationNewItemAria": { - "message": "New Item, opens in new window", + "message": "Nuovo elemento, si apre in una nuova finestra", "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" }, "notificationEditTooltip": { - "message": "Edit before saving", + "message": "Modifica prima di salvare", "description": "Tooltip and Aria label for edit button on cipher item" }, "newNotification": { - "message": "New notification" + "message": "Nuova notifica" }, "labelWithNotification": { - "message": "$LABEL$: New notification", + "message": "$LABEL$: Nuova notifica", "description": "Label for the notification with a new login suggestion.", "placeholders": { "label": { @@ -1093,15 +1093,15 @@ } }, "notificationLoginSaveConfirmation": { - "message": "saved to Bitwarden.", + "message": "salvato in Bitwarden.", "description": "Shown to user after item is saved." }, "notificationLoginUpdatedConfirmation": { - "message": "updated in Bitwarden.", + "message": "aggiornato in Bitwarden.", "description": "Shown to user after item is updated." }, "selectItemAriaLabel": { - "message": "Select $ITEMTYPE$, $ITEMNAME$", + "message": "Seleziona $ITEMTYPE$, $ITEMNAME$", "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", "placeholders": { "itemType": { @@ -1121,15 +1121,15 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "Sblocca per salvare questo login", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { - "message": "Save login", + "message": "Salva il login", "description": "Prompt asking the user if they want to save their login details." }, "updateLogin": { - "message": "Update existing login", + "message": "Aggiorna login esistente", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -1141,7 +1141,7 @@ "description": "Message displayed when login details are successfully updated." }, "loginUpdateTaskSuccess": { - "message": "Great job! You took the steps to make you and $ORGANIZATION$ more secure.", + "message": "Congratulazioni! Hai reso $ORGANIZATION$ e te stesso più sicuri.", "placeholders": { "organization": { "content": "$1" @@ -1150,7 +1150,7 @@ "description": "Shown to user after login is updated." }, "loginUpdateTaskSuccessAdditional": { - "message": "Thank you for making $ORGANIZATION$ more secure. You have $TASK_COUNT$ more passwords to update.", + "message": "Grazie per aver reso $ORGANIZATION$ più sicuro. Hai altre $TASK_COUNT$ password da aggiornare.", "placeholders": { "organization": { "content": "$1" @@ -1162,7 +1162,7 @@ "description": "Shown to user after login is updated." }, "nextSecurityTaskAction": { - "message": "Change next password", + "message": "Cambia la prossima password", "description": "Message prompting user to undertake completion of another security task." }, "saveFailure": { @@ -1600,13 +1600,13 @@ "message": "Suggerimenti per il riempimento automatico" }, "autofillSpotlightTitle": { - "message": "Easily find autofill suggestions" + "message": "Trova facilmente suggerimenti di riempimento automatico" }, "autofillSpotlightDesc": { - "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + "message": "Disattiva le impostazioni di riempimento automatico del tuo browser, in modo da non entrare in conflitto con Bitwarden." }, "turnOffBrowserAutofill": { - "message": "Turn off $BROWSER$ autofill", + "message": "Disattiva il riempimento automatico di $BROWSER$", "placeholders": { "browser": { "content": "$1", @@ -1615,7 +1615,7 @@ } }, "turnOffAutofill": { - "message": "Turn off autofill" + "message": "Disattiva il riempimento automatico" }, "showInlineMenuLabel": { "message": "Mostra suggerimenti di riempimento automatico nei campi del modulo" @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Usa questa password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Usa questo nome utente" }, @@ -2374,7 +2377,7 @@ "message": "Informativa sulla Privacy" }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "La tua nuova password non può essere la stessa della tua password attuale." }, "hintEqualsPassword": { "message": "Il suggerimento della password non può essere uguale alla password." @@ -2584,14 +2587,14 @@ "description": "Description of the review at-risk login slide on the at-risk password page carousel" }, "reviewAtRiskLoginSlideImgAltPeriod": { - "message": "Illustration of a list of logins that are at-risk." + "message": "Illustrazione di una lista di login a rischio." }, "generatePasswordSlideDesc": { "message": "Genera rapidamente una parola d'accesso forte e unica con il menu' di riempimento automatico Bitwarden nel sito a rischio.", "description": "Description of the generate password slide on the at-risk password page carousel" }, "generatePasswordSlideImgAltPeriod": { - "message": "Illustration of the Bitwarden autofill menu displaying a generated password." + "message": "Illustrazione del menu di riempimento automatico Bitwarden che mostra una password generata." }, "updateInBitwarden": { "message": "Aggiorna in Bitwarden" @@ -2601,7 +2604,7 @@ "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" }, "updateInBitwardenSlideImgAltPeriod": { - "message": "Illustration of a Bitwarden’s notification prompting the user to update the login." + "message": "Illustrazione di una notifica Bitwarden che richiede all'utente di aggiornare il login." }, "turnOnAutofill": { "message": "Attiva riempimento automatico" @@ -3015,13 +3018,13 @@ "message": "Nessun identificatore univoco trovato." }, "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + "message": "La password principale non è più richiesta per i membri dell'organizzazione. Per favore, conferma il dominio qui sotto con l'amministratore." }, "organizationName": { - "message": "Organization name" + "message": "Nome organizzazione" }, "keyConnectorDomain": { - "message": "Key Connector domain" + "message": "Dominio Key Connector" }, "leaveOrganization": { "message": "Lascia organizzazione" @@ -3057,7 +3060,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included", + "message": "Solo gli elementi della cassaforte personale associati a $EMAIL$, includendo gli allegati, saranno esportati. Gli elementi della cassaforte dell'organizzazione non saranno inclusi", "placeholders": { "email": { "content": "$1", @@ -3584,28 +3587,28 @@ "message": "Dispositivo fidato" }, "trustOrganization": { - "message": "Trust organization" + "message": "Fidati dell'organizzazione" }, "trust": { - "message": "Trust" + "message": "Fidati" }, "doNotTrust": { - "message": "Do not trust" + "message": "Non fidarti" }, "organizationNotTrusted": { - "message": "Organization is not trusted" + "message": "L'organizzazione non è fidata" }, "emergencyAccessTrustWarning": { - "message": "For the security of your account, only confirm if you have granted emergency access to this user and their fingerprint matches what is displayed in their account" + "message": "Per la sicurezza del tuo account, conferma solo se hai concesso l'accesso di emergenza a questo utente e le loro impronte digitali corrispondono a quelle contenute nel loro account" }, "orgTrustWarning": { - "message": "For the security of your account, only proceed if you are a member of this organization, have account recovery enabled, and the fingerprint displayed below matches the organization's fingerprint." + "message": "Per la sicurezza del tuo account, procedi solo se sei un membro di questa organizzazione, il recupero dell'account è abilitato e l'impronta digitale visualizzata di seguito corrisponde all'impronta digitale dell'organizzazione." }, "orgTrustWarning1": { - "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + "message": "Questa organizzazione ha una politica Enterprise che ti iscriverà al recupero dell'account. La registrazione consentirà agli amministratori dell'organizzazione di modificare la password. Procedi solo se riconosci questa organizzazione e la frase di impronta digitale mostrata di seguito corrisponde all'impronta digitale dell'organizzazione." }, "trustUser": { - "message": "Trust user" + "message": "Fidati dell'utente" }, "sendsNoItemsTitle": { "message": "Nessun Send attivo", @@ -3616,11 +3619,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsTitleNoItems": { - "message": "Send sensitive information safely", + "message": "Invia informazioni sensibili in modo sicuro", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsBodyNoItems": { - "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "message": "Condividi file e dati in modo sicuro con chiunque, su qualsiasi piattaforma. Le tue informazioni saranno crittografate end-to-end.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4355,7 +4358,7 @@ } }, "viewItemTitleWithField": { - "message": "View item - $ITEMNAME$ - $FIELD$", + "message": "Visualizza elemento - $ITEMNAME$ - $FIELD$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4379,7 +4382,7 @@ } }, "autofillTitleWithField": { - "message": "Autofill - $ITEMNAME$ - $FIELD$", + "message": "Riempimento automatico - $ITEMNAME$ - $FIELD$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4540,31 +4543,31 @@ } }, "downloadBitwarden": { - "message": "Download Bitwarden" + "message": "Scarica Bitwarden" }, "downloadBitwardenOnAllDevices": { - "message": "Download Bitwarden on all devices" + "message": "Scarica Bitwarden su tutti i dispositivi" }, "getTheMobileApp": { - "message": "Get the mobile app" + "message": "Scarica l'app mobile" }, "getTheMobileAppDesc": { - "message": "Access your passwords on the go with the Bitwarden mobile app." + "message": "Accedi alle tue password ovunque con l'app Bitwarden per dispositivi mobili." }, "getTheDesktopApp": { - "message": "Get the desktop app" + "message": "Scarica l'app desktop" }, "getTheDesktopAppDesc": { "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." }, "downloadFromBitwardenNow": { - "message": "Download from bitwarden.com now" + "message": "Scarica ora da bitwarden.com" }, "getItOnGooglePlay": { - "message": "Get it on Google Play" + "message": "Disponible su Google Play" }, "downloadOnTheAppStore": { - "message": "Download on the App Store" + "message": "Scarica dall'App Store" }, "permanentlyDeleteAttachmentConfirmation": { "message": "Sei sicuro di voler eliminare definitivamente questo allegato?" @@ -5028,13 +5031,13 @@ "message": "Lo sblocco biometrico non è attualmente disponibile per un motivo sconosciuto." }, "unlockVault": { - "message": "Unlock your vault in seconds" + "message": "Sblocca la cassaforte in secondi" }, "unlockVaultDesc": { "message": "You can customize your unlock and timeout settings to more quickly access your vault." }, "unlockPinSet": { - "message": "Unlock PIN set" + "message": "Sblocca PIN impostato" }, "authenticating": { "message": "Autenticazione" @@ -5048,7 +5051,7 @@ "description": "Notification message for when a password has been regenerated" }, "saveToBitwarden": { - "message": "Save to Bitwarden", + "message": "Salva su Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5253,13 +5256,13 @@ "message": "Cambia parola d'accesso a rischio" }, "settingsVaultOptions": { - "message": "Vault options" + "message": "Opzioni cassaforte" }, "emptyVaultDescription": { "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." }, "introCarouselLabel": { - "message": "Welcome to Bitwarden" + "message": "Benvenuto su Bitwarden" }, "securityPrioritized": { "message": "Security, prioritized" diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 5c235bdea43..b4f1fa0132e 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "このパスワードを使用する" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "このユーザー名を使用する" }, diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 7f36bb3568f..21ff426dfc5 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 4775d1f7af0..feb5a7706f3 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index ac36d911e89..2fa7ea8013e 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 5a496cde98a..2655ef688e7 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "이 비밀번호 사용" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "이 사용자 이름 사용" }, diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index f790c226437..07e26a862b3 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index f47b5f7645e..5a8a82c16ed 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Izmantot šo paroli" }, + "useThisPassphrase": { + "message": "Izmantot šo paroles vārdkopu" + }, "useThisUsername": { "message": "Izmantot šo lietotājvārdu" }, @@ -5362,7 +5365,7 @@ "message": "Ātra paroļu izveidošana" }, "generatorNudgeBodyOne": { - "message": "Vienkārša spēcīgu un neatkārtojamu paroļu izveidošana ar pogu", + "message": "Vienkārša spēcīgu un neatkārtojamu paroļu izveidošana ar", "description": "Two part message", "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index ab0120a40e2..a56fb396435 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 625bf093ba9..416862859c9 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 4775d1f7af0..feb5a7706f3 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index a11d5de6e2a..504f98fe44a 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Bruk dette passordet" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Bruk dette brukernavnet" }, diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 4775d1f7af0..feb5a7706f3 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 67a784459b2..4d60287a78e 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -468,7 +468,7 @@ "message": "Wachtwoord gegenereerd" }, "passphraseGenerated": { - "message": "Wachtwoorden gegenereerd" + "message": "Wachtwoordzin gegenereerd" }, "usernameGenerated": { "message": "Gebruikersnaam gegenereerd" @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Dit wachtwoord gebruiken" }, + "useThisPassphrase": { + "message": "Deze wachtwoordzin gebruiken" + }, "useThisUsername": { "message": "Deze gebruikersnaam gebruiken" }, diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 4775d1f7af0..feb5a7706f3 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 4775d1f7af0..feb5a7706f3 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index eb391f5e34b..32d6799493e 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Użyj tego hasła" }, + "useThisPassphrase": { + "message": "Użyj tego hasła wyrazowego" + }, "useThisUsername": { "message": "Użyj tej nazwy użytkownika" }, diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index c0b83248edf..7d70debf128 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use esta senha" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use este nome de usuário" }, diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index a144f5767d4..67db6178ace 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Utilizar esta palavra-passe" }, + "useThisPassphrase": { + "message": "Utilizar esta frase de acesso" + }, "useThisUsername": { "message": "Utilizar este nome de utilizador" }, diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 2aabf825399..3ed8e876d6f 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index a23ab4172f8..d7f901bf04f 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Использовать этот пароль" }, + "useThisPassphrase": { + "message": "Использовать эту парольную фразу" + }, "useThisUsername": { "message": "Использовать это имя пользователя" }, diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index eafbf9c584e..2e20f5dc4ce 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 016f4ca1f5a..8d6f193168e 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -474,7 +474,7 @@ "message": "Používateľské meno vygenerované" }, "emailGenerated": { - "message": "E-mail vygenoravný" + "message": "E-mail vygenerovaný" }, "regeneratePassword": { "message": "Vygenerovať nové heslo" @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Použiť toto heslo" }, + "useThisPassphrase": { + "message": "Použiť túto prístupovú frázu" + }, "useThisUsername": { "message": "Použiť toto používateľské meno" }, @@ -5372,7 +5375,7 @@ "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyAria": { - "message": "Jednoducho vytvorte silné a jedinečné heslá kliknutím na tlačidlo Generovať heslo, aby zabezpečili prihlasovacie údaje.", + "message": "Jednoducho vytvorte silné a jedinečné heslá kliknutím na tlačidlo Generovať heslo, aby ste zabezpečili prihlasovacie údaje.", "description": "Aria label for the body content of the generator nudge" }, "noPermissionsViewPage": { diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 3f84455be88..224f31076b8 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index dae425fcfd3..54371488c0d 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Употреби ову лозинку" }, + "useThisPassphrase": { + "message": "Употреби ову приступну фразу" + }, "useThisUsername": { "message": "Употреби ово корисничко име" }, @@ -3616,11 +3619,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsTitleNoItems": { - "message": "Send sensitive information safely", + "message": "Шаљите безбедно осетљиве информације", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsBodyNoItems": { - "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "message": "Делите датотеке и податке безбедно са било ким, на било којој платформи. Ваше информације ће остати шифроване од почетка-до-краја уз ограничење изложености.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -5028,13 +5031,13 @@ "message": "Биометријско откључавање није доступно из непознатог разлога." }, "unlockVault": { - "message": "Unlock your vault in seconds" + "message": "Откључајте сеф у секунди" }, "unlockVaultDesc": { - "message": "You can customize your unlock and timeout settings to more quickly access your vault." + "message": "Можете да прилагодите своје поставке за откључавање и истек времена да бисте брзо приступили сефу." }, "unlockPinSet": { - "message": "Unlock PIN set" + "message": "Постављен ПИН деблокирања" }, "authenticating": { "message": "Аутентификација" @@ -5359,20 +5362,20 @@ "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "generatorNudgeTitle": { - "message": "Quickly create passwords" + "message": "Брзо креирајте лозинке" }, "generatorNudgeBodyOne": { - "message": "Easily create strong and unique passwords by clicking on", + "message": "Лако креирајте снажне и јединствене лозинке кликом на", "description": "Two part message", "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyTwo": { - "message": "to help you keep your logins secure.", + "message": "да вам помогне да задржите своје пријаве сигурно.", "description": "Two part message", "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyAria": { - "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "message": "Лако креирајте снажне и јединствене лозинке кликом на дугме „Генерирате лозинку“ да вам помогне да чувате своје пријаве на сигурно.", "description": "Aria label for the body content of the generator nudge" }, "noPermissionsViewPage": { diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index f57209122ef..10b9d965cc8 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -246,7 +246,7 @@ "message": "Logga in i ditt valv" }, "autoFillInfo": { - "message": "Det finns inga inloggningar tillgängliga för automatisk ifyllnad på den nuvarande fliken." + "message": "Det finns inga inloggningar tillgängliga för autofyll på den nuvarande fliken." }, "addLogin": { "message": "Lägg till en inloggning" @@ -383,7 +383,7 @@ "message": "Redigera mapp" }, "editFolderWithName": { - "message": "Edit folder: $FOLDERNAME$", + "message": "Redigera mapp: $FOLDERNAME$", "placeholders": { "foldername": { "content": "$1", @@ -575,7 +575,7 @@ "message": "Favorit" }, "unfavorite": { - "message": "Unfavorite" + "message": "Ta bort favorit" }, "itemAddedToFavorites": { "message": "Objekt tillagt i favoriter" @@ -842,7 +842,7 @@ "message": "Scan authenticator QR code from current webpage" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "Gör tvåstegsverifiering sömlös" }, "totpHelper": { "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." @@ -872,13 +872,13 @@ "message": "Logga in på Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "Ange koden som skickats till din e-post" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "Ange koden från din autentiseringsapp" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "Tryck på din YubiKey för att autentisera" }, "duoTwoFactorRequiredPageSubtitle": { "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." @@ -890,16 +890,16 @@ "message": "Follow the steps below to finish logging in with your security key." }, "restartRegistration": { - "message": "Restart registration" + "message": "Starta om registrering" }, "expiredLink": { - "message": "Expired link" + "message": "Utgången länk" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "Starta om registreringen eller försök logga in." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Du kanske redan har ett konto" }, "logOutConfirmation": { "message": "Är du säker på att du vill logga ut?" @@ -911,7 +911,7 @@ "message": "Nej" }, "location": { - "message": "Location" + "message": "Plats" }, "unexpectedError": { "message": "Ett okänt fel har inträffat." @@ -926,10 +926,10 @@ "message": "Tvåstegsverifiering gör ditt konto säkrare genom att kräva att du verifierar din inloggning med en annan enhet, t.ex. en säkerhetsnyckel, autentiseringsapp, SMS, telefonsamtal eller e-post. Tvåstegsverifiering kan aktiveras i Bitwardens webbvalv. Vill du besöka webbplatsen nu?" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "Gör ditt konto säkrare genom att konfigurera tvåstegsverifiering i Bitwardens webbapp." }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "Fortsätt till webbapp?" }, "editedFolder": { "message": "Mapp sparad" @@ -1031,7 +1031,7 @@ "message": "Visa kort på fliksida" }, "showCardsCurrentTabDesc": { - "message": "Lista kortobjekt på fliksidan för enkel automatisk fyllning." + "message": "Lista kortobjekt på fliksidan för enkel autofyll." }, "showIdentitiesInVaultViewV2": { "message": "Always show identities as Autofill suggestions on Vault view" @@ -1040,7 +1040,7 @@ "message": "Visa identiteter på fliksidan" }, "showIdentitiesCurrentTabDesc": { - "message": "Lista identitetsobjekt på fliksidan för enkel automatisk fyllning." + "message": "Lista identitetsobjekt på fliksidan för enkel autofyll." }, "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" @@ -1125,7 +1125,7 @@ "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { - "message": "Save login", + "message": "Spara inloggning", "description": "Prompt asking the user if they want to save their login details." }, "updateLogin": { @@ -1133,11 +1133,11 @@ "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { - "message": "Login saved", + "message": "Inloggning sparad", "description": "Message displayed when login details are successfully saved." }, "loginUpdateSuccess": { - "message": "Login updated", + "message": "Inloggning uppdaterad", "description": "Message displayed when login details are successfully updated." }, "loginUpdateTaskSuccess": { @@ -1195,7 +1195,7 @@ "message": "Uppdatera" }, "notificationUnlockDesc": { - "message": "Lås upp ditt Bitwarden-valv för att slutföra begäran om automatisk ifyllnad." + "message": "Lås upp ditt Bitwarden-valv för att slutföra begäran om autofyll." }, "notificationUnlock": { "message": "Lås upp" @@ -1217,7 +1217,7 @@ "description": "Default URI match detection for autofill." }, "defaultUriMatchDetectionDesc": { - "message": "Välj standardalternativet för hur matchning av URI är hanterat för inloggningar när du utför operationer såsom automatisk ifyllnad." + "message": "Välj standardalternativet för hur matchning av URI är hanterat för inloggningar när du utför operationer såsom autofyll." }, "theme": { "message": "Tema" @@ -1274,7 +1274,7 @@ "description": "WARNING (should stay in capitalized letters if the language permits)" }, "warningCapitalized": { - "message": "Warning", + "message": "Varning", "description": "Warning (should maintain locale-relevant capitalization)" }, "confirmVaultExport": { @@ -1474,29 +1474,29 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "Fråga inte igen på den här enheten i 30 dagar" }, "selectAnotherMethod": { - "message": "Select another method", + "message": "Välj en annan metod", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "Använd din återställningskod" }, "insertU2f": { "message": "Sätt i din säkerhetsnyckel i en av datorns USB-portar. Om nyckeln har en knapp, sätt fingret på den." }, "openInNewTab": { - "message": "Open in new tab" + "message": "Öppna i ny flik" }, "webAuthnAuthenticate": { "message": "Autentisera WebAuthn" }, "readSecurityKey": { - "message": "Read security key" + "message": "Läs säkerhetsnyckel" }, "awaitingSecurityKeyInteraction": { - "message": "Awaiting security key interaction..." + "message": "Väntar på interaktion med säkerhetsnyckel..." }, "loginUnavailable": { "message": "Inloggning ej tillgänglig" @@ -1511,7 +1511,7 @@ "message": "Alternativ för tvåstegsverifiering" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "Välj metod för tvåstegsverifiering" }, "recoveryCodeDesc": { "message": "Förlorat åtkomst till alla dina metoder för tvåstegsverifiering? Använd din återställningskod för att inaktivera tvåstegsverifiering på ditt konto." @@ -1523,7 +1523,7 @@ "message": "Autentiseringsapp" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "Ange en kod som genererats av en autentiseringsapp som Bitwarden Authenticator.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { @@ -1556,13 +1556,13 @@ "message": "Egen-hostad miljö" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "Ange bas-URL:en för din självhostade Bitwarden-installation. Exempel: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "För avancerad konfiguration kan du ange bas-URL för varje tjänst separat." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "Du måste lägga till antingen serverns bas-URL eller minst en anpassad miljö." }, "customEnvironment": { "message": "Anpassad miljö" @@ -1571,7 +1571,7 @@ "message": "Server-URL" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "Självhostad server-URL", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1593,20 +1593,20 @@ "message": "Miljö-URL:erna har sparats" }, "showAutoFillMenuOnFormFields": { - "message": "Visa menyn för automatisk ifyllnad på formulärfält", + "message": "Visa menyn för autofyll på formulärfält", "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { "message": "Förslag för autofyll" }, "autofillSpotlightTitle": { - "message": "Easily find autofill suggestions" + "message": "Hitta förslag på autofyll enkelt" }, "autofillSpotlightDesc": { - "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + "message": "Stäng av webbläsarens autofyllinställningar så att de inte orsakar konflikt med Bitwarden." }, "turnOffBrowserAutofill": { - "message": "Turn off $BROWSER$ autofill", + "message": "Stäng av $BROWSER$ autofyll", "placeholders": { "browser": { "content": "$1", @@ -1615,7 +1615,7 @@ } }, "turnOffAutofill": { - "message": "Turn off autofill" + "message": "Stäng av autofyll" }, "showInlineMenuLabel": { "message": "Visa förslag för autofyll i formulärfält" @@ -1627,13 +1627,13 @@ "message": "Visa kort som förslag" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "Visa förslag när ikonen markerats" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "Gäller för alla inloggade konton." }, "turnOffBrowserBuiltInPasswordManagerSettings": { - "message": "Turn off your browser's built in password manager settings to avoid conflicts." + "message": "Stäng av webbläsarens inbyggda lösenordshanterarinställningar för att undvika konflikter." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { "message": "Redigera webbläsarinställningar." @@ -1643,11 +1643,11 @@ "description": "Overlay setting select option for disabling autofill overlay" }, "autofillOverlayVisibilityOnFieldFocus": { - "message": "When field is selected (on focus)", + "message": "När fältet är markerat (i fokus)", "description": "Overlay appearance select option for showing the field on focus of the input element" }, "autofillOverlayVisibilityOnButtonClick": { - "message": "När ikonen för automatisk ifyllnad är vald", + "message": "När ikonen för autofyll är vald", "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { @@ -1693,13 +1693,13 @@ "message": "Öppna valvet i sidofältet" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "Autofyll den senast använda inloggningen för den aktuella webbplatsen" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "Autofyll det senast använda kortet för den aktuella webbplatsen" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "Autofyll den senast använda identiteten för den aktuella webbplatsen" }, "commandGeneratePasswordDesc": { "message": "Skapa och kopiera ett nytt slumpmässigt lösenord till urklipp." @@ -1723,7 +1723,7 @@ "message": "Dra för att sortera" }, "dragToReorder": { - "message": "Drag to reorder" + "message": "Dra för att ändra ordning" }, "cfTypeText": { "message": "Text" @@ -1758,7 +1758,7 @@ "message": "Visa en identifierbar bild bredvid varje inloggning." }, "faviconDescAlt": { - "message": "Show a recognizable image next to each login. Applies to all logged in accounts." + "message": "Visa en igenkännbar bild bredvid varje inloggning. Gäller för alla inloggade konton." }, "enableBadgeCounter": { "message": "Visa aktivitetsräknaren" @@ -1959,7 +1959,7 @@ "message": "Rensa generatorhistorik" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Om du fortsätter kommer alla poster att raderas permanent från generatorns historik. Är du säker på att du vill fortsätta?" }, "back": { "message": "Tillbaka" @@ -1998,7 +1998,7 @@ "message": "Säkra anteckningar" }, "sshKeys": { - "message": "SSH Keys" + "message": "SSH-nycklar" }, "clear": { "message": "Rensa", @@ -2081,10 +2081,10 @@ "message": "Rensa historik" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Inget tillgängligt innehåll" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Du har inte genererat något nyligen" }, "remove": { "message": "Ta bort" @@ -2154,7 +2154,7 @@ "message": "Ange en PIN-kod för att låsa upp Bitwarden. Dina PIN-inställningar återställs om du någonsin loggar ut helt från programmet." }, "setYourPinCode1": { - "message": "Your PIN will be used to unlock Bitwarden instead of your master password. Your PIN will reset if you ever fully log out of Bitwarden." + "message": "Din PIN-kod kommer att användas för att låsa upp Bitwarden istället för ditt huvudlösenord. Din PIN-kod kommer att återställas om du någonsin helt loggar ut från Bitwarden." }, "pinRequired": { "message": "PIN-kod krävs." @@ -2181,7 +2181,7 @@ "message": "Lås med huvudlösenordet vid omstart av webbläsaren" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "Kräv huvudlösenord vid omstart av webbläsaren" }, "selectOneCollection": { "message": "Du måste markera minst en samling." @@ -2196,36 +2196,39 @@ "message": "Lösenordsgenerator" }, "usernameGenerator": { - "message": "Username generator" + "message": "Användarnamnsgenerator" }, "useThisEmail": { - "message": "Use this email" + "message": "Använd denna e-post" }, "useThisPassword": { - "message": "Use this password" + "message": "Använd detta lösenord" + }, + "useThisPassphrase": { + "message": "Use this passphrase" }, "useThisUsername": { - "message": "Use this username" + "message": "Använd detta användarnamn" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "Säkert lösenord genererat! Glöm inte att även uppdatera ditt lösenord på webbplatsen." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "Använd generatorn", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "för att skapa ett starkt unikt lösenord", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "vaultCustomization": { - "message": "Vault customization" + "message": "Anpassning av valv" }, "vaultTimeoutAction": { "message": "Åtgärd när valvets tidsgräns överskrids" }, "vaultTimeoutAction1": { - "message": "Timeout action" + "message": "Åtgärd vid timeout" }, "lock": { "message": "Lås", @@ -2347,16 +2350,16 @@ "message": "Ditt nya huvudlösenord uppfyller inte kraven i policyn." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "Få råd, nyheter och forskningsmöjligheter från Bitwarden i din inkorg." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "Avprenumerera" }, "atAnyTime": { "message": "när som helst." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "Genom att fortsätta godkänner du" }, "and": { "message": "och" @@ -2871,7 +2874,7 @@ "message": "E-postverifiering krävs" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "E-post verifierad" }, "emailVerificationRequiredDesc": { "message": "Du måste verifiera din e-postadress för att använda den här funktionen. Du kan verifiera din e-postadress i webbvalvet." @@ -3018,7 +3021,7 @@ "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." }, "organizationName": { - "message": "Organization name" + "message": "Organisationsnamn" }, "keyConnectorDomain": { "message": "Key Connector domain" @@ -3777,7 +3780,7 @@ "description": "Text to display in overlay when the account is locked." }, "unlockYourAccountToViewAutofillSuggestions": { - "message": "Unlock your account to view autofill suggestions", + "message": "Lås upp ditt konto för att visa förslag för autofyll", "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { @@ -4238,7 +4241,7 @@ "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { - "message": "Ignoring this option may cause conflicts between Bitwarden autofill suggestions and your browser's.", + "message": "Att ignorera det här alternativet kan orsaka konflikter mellan Bitwardens autofyllförslag och webbläsarens.", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { @@ -4287,13 +4290,13 @@ "message": "Passkey borttagen" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "Förslag för autofyll" }, "itemSuggestions": { "message": "Suggested items" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to autofill" + "message": "Spara ett inloggningsobjekt för den här webbplatsen för autofyll" }, "yourVaultIsEmpty": { "message": "Ditt valv är tomt" @@ -4926,7 +4929,7 @@ "message": "Kontoåtgärder" }, "showNumberOfAutofillSuggestions": { - "message": "Show number of login autofill suggestions on extension icon" + "message": "Visa antal autofyllförslag för inloggning på tilläggsikonen" }, "showQuickCopyActions": { "message": "Show quick copy actions on Vault" @@ -5323,7 +5326,7 @@ "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "så att den här inloggningen visas som ett förslag för autofyll.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 4775d1f7af0..feb5a7706f3 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index fd9bac62391..f18f2ac9ba9 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 02d7d15b8a2..ded4568adaa 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Bu parolayı kullan" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Bu kullanıcı adını kullan" }, diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 9406aabe088..952c223f262 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -1072,7 +1072,7 @@ "description": "Aria label for the view button in notification bar confirmation message" }, "notificationNewItemAria": { - "message": "New Item, opens in new window", + "message": "Новий запис, відкривається у новому вікні", "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" }, "notificationEditTooltip": { @@ -1093,15 +1093,15 @@ } }, "notificationLoginSaveConfirmation": { - "message": "saved to Bitwarden.", + "message": "збережено до Bitwarden.", "description": "Shown to user after item is saved." }, "notificationLoginUpdatedConfirmation": { - "message": "updated in Bitwarden.", + "message": "оновлено в Bitwarden.", "description": "Shown to user after item is updated." }, "selectItemAriaLabel": { - "message": "Select $ITEMTYPE$, $ITEMNAME$", + "message": "Вибрати $ITEMTYPE$, $ITEMNAME$", "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", "placeholders": { "itemType": { @@ -1121,7 +1121,7 @@ "description": "Button text for updating an existing login entry." }, "unlockToSave": { - "message": "Unlock to save this login", + "message": "Розблокуйте, щоб зберегти цей запис", "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Використати цей пароль" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Використати це ім'я користувача" }, @@ -3015,13 +3018,13 @@ "message": "Не знайдено унікальний ідентифікатор." }, "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + "message": "Головний пароль більше не є обов'язковим для учасників зазначеної організації. Підтвердьте вказаний нижче домен з адміністратором вашої організації." }, "organizationName": { - "message": "Organization name" + "message": "Назва організації" }, "keyConnectorDomain": { - "message": "Key Connector domain" + "message": "Домен Key Connector" }, "leaveOrganization": { "message": "Покинути організацію" @@ -3616,11 +3619,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsTitleNoItems": { - "message": "Send sensitive information safely", + "message": "Безпечно надсилайте конфіденційну інформацію", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsBodyNoItems": { - "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "message": "Безпечно діліться файлами й даними з ким завгодно, на будь-якій платформі. Ваша інформація наскрізно зашифрована та має обмежений доступ.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -5028,13 +5031,13 @@ "message": "Біометричне розблокування зараз недоступне з невідомої причини." }, "unlockVault": { - "message": "Unlock your vault in seconds" + "message": "Розблоковуйте сховище за секунди" }, "unlockVaultDesc": { - "message": "You can customize your unlock and timeout settings to more quickly access your vault." + "message": "Ви можете налаштувати розблокування і час очікування для швидшого доступу до сховища." }, "unlockPinSet": { - "message": "Unlock PIN set" + "message": "Розблокування PIN-кодом встановлено" }, "authenticating": { "message": "Аутентифікація" @@ -5286,7 +5289,7 @@ "message": "Зберігайте скільки завгодно паролів на необмеженій кількості пристроїв, використовуючи Bitwarden для мобільних пристроїв, браузерів та комп'ютерів." }, "nudgeBadgeAria": { - "message": "1 notification" + "message": "1 сповіщення" }, "emptyVaultNudgeTitle": { "message": "Імпортуйте наявні паролі" @@ -5301,13 +5304,13 @@ "message": "Вітаємо у вашому сховищі!" }, "hasItemsVaultNudgeBodyOne": { - "message": "Autofill items for the current page" + "message": "Автозаповнення записів для поточної сторінки" }, "hasItemsVaultNudgeBodyTwo": { - "message": "Favorite items for easy access" + "message": "Обрані записи для швидкого доступу" }, "hasItemsVaultNudgeBodyThree": { - "message": "Search your vault for something else" + "message": "Пошук інших елементів у сховищі" }, "newLoginNudgeTitle": { "message": "Заощаджуйте час з автозаповненням" @@ -5359,23 +5362,23 @@ "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "generatorNudgeTitle": { - "message": "Quickly create passwords" + "message": "Швидко створюйте паролі" }, "generatorNudgeBodyOne": { - "message": "Easily create strong and unique passwords by clicking on", + "message": "Легко створюйте надійні та унікальні паролі, натиснувши на", "description": "Two part message", "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyTwo": { - "message": "to help you keep your logins secure.", + "message": "щоб зберегти свої записи в безпеці.", "description": "Two part message", "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyAria": { - "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "message": "Легко створюйте надійні та унікальні паролі, натиснувши кнопку Генерувати пароль, щоб зберегти свої записи в безпеці.", "description": "Aria label for the body content of the generator nudge" }, "noPermissionsViewPage": { - "message": "You do not have permissions to view this page. Try logging in with a different account." + "message": "У вас немає дозволу переглядати цю сторінку. Спробуйте ввійти з іншим обліковим записом." } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 29a7b7109e7..4db1394023d 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 8a537be08f2..18cbc9c10d3 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "使用此密码" }, + "useThisPassphrase": { + "message": "使用此密码短语" + }, "useThisUsername": { "message": "使用此用户名" }, @@ -4555,7 +4558,7 @@ "message": "获取桌面 App" }, "getTheDesktopAppDesc": { - "message": "无需浏览器也可访问您的密码库。在桌面 App 和浏览器扩展中设置生物识别解锁,以实现快速解锁。" + "message": "无需使用浏览器访问您的密码库,在桌面 App 和浏览器扩展中同时设置生物识别解锁,即可实现快速解锁。" }, "downloadFromBitwardenNow": { "message": "立即从 bitwarden.com 下载" @@ -5031,7 +5034,7 @@ "message": "数秒内解锁您的密码库" }, "unlockVaultDesc": { - "message": "您可以自定义解锁和超时设置,以便更快地访问您的密码库。" + "message": "您可以自定义解锁和超时设置,以便更快速地访问您的密码库。" }, "unlockPinSet": { "message": "解锁 PIN 设置" @@ -5235,7 +5238,7 @@ "message": "SSH 密钥导入成功" }, "cannotRemoveViewOnlyCollections": { - "message": "您无法删除仅具有「查看」权限的集合:$COLLECTIONS$", + "message": "您无法移除仅具有「查看」权限的集合:$COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -5304,7 +5307,7 @@ "message": "为当前页面自动填充项目" }, "hasItemsVaultNudgeBodyTwo": { - "message": "收藏项目以便快速访问" + "message": "收藏项目以便轻松访问" }, "hasItemsVaultNudgeBodyThree": { "message": "在密码库中搜索其他内容" @@ -5323,7 +5326,7 @@ "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "以便将此登录显示为自动填充建议。", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, @@ -5372,7 +5375,7 @@ "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." }, "generatorNudgeBodyAria": { - "message": "一键创建强大且唯一的密码,帮助您保持登录安全。", + "message": "点击「生成密码」按钮,轻松创建强大且唯一的密码,帮助您保持登录安全。", "description": "Aria label for the body content of the generator nudge" }, "noPermissionsViewPage": { diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 9d4eabb1b49..639469b7cd4 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -2204,6 +2204,9 @@ "useThisPassword": { "message": "使用此密碼" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "使用此使用者名稱" }, From 0f6d4a92d7d269873d06b67029bedf87d4267614 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Fri, 30 May 2025 09:37:08 +0200 Subject: [PATCH 187/245] Migrate libs/tools/card to be owned by DIRT (#14987) Co-authored-by: Daniel James Smith --- .github/CODEOWNERS | 1 + .storybook/main.ts | 4 ++-- apps/browser/tsconfig.json | 2 +- apps/desktop/tsconfig.json | 2 +- apps/web/tsconfig.json | 2 +- bitwarden_license/bit-common/tsconfig.json | 2 +- .../all-applications.component.html | 8 ++++---- .../all-applications.component.ts | 2 +- .../critical-applications.component.html | 8 ++++---- .../critical-applications.component.ts | 2 +- bitwarden_license/bit-web/tsconfig.json | 2 +- libs/dirt/card/README.md | 5 +++++ libs/{tools => dirt}/card/jest.config.js | 0 libs/{tools => dirt}/card/package.json | 2 +- .../card/src/card.component.html | 0 .../{tools => dirt}/card/src/card.component.ts | 2 +- libs/{tools => dirt}/card/src/card.stories.ts | 4 ++-- libs/{tools => dirt}/card/src/index.ts | 0 libs/{tools => dirt}/card/test.setup.ts | 0 libs/{tools => dirt}/card/tsconfig.json | 0 libs/{tools => dirt}/card/tsconfig.spec.json | 0 libs/shared/tsconfig.spec.json | 2 +- libs/tools/card/README.md | 5 ----- package-lock.json | 18 +++++++++--------- tsconfig.eslint.json | 2 +- tsconfig.json | 4 ++-- 26 files changed, 40 insertions(+), 39 deletions(-) create mode 100644 libs/dirt/card/README.md rename libs/{tools => dirt}/card/jest.config.js (100%) rename libs/{tools => dirt}/card/package.json (92%) rename libs/{tools => dirt}/card/src/card.component.html (100%) rename libs/{tools => dirt}/card/src/card.component.ts (97%) rename libs/{tools => dirt}/card/src/card.stories.ts (87%) rename libs/{tools => dirt}/card/src/index.ts (100%) rename libs/{tools => dirt}/card/test.setup.ts (100%) rename libs/{tools => dirt}/card/tsconfig.json (100%) rename libs/{tools => dirt}/card/tsconfig.spec.json (100%) delete mode 100644 libs/tools/card/README.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 38a1597848e..def03c714d7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -39,6 +39,7 @@ libs/tools @bitwarden/team-tools-dev apps/web/src/app/dirt @bitwarden/team-data-insights-and-reporting-dev bitwarden_license/bit-common/src/dirt @bitwarden/team-data-insights-and-reporting-dev bitwarden_license/bit-web/src/app/dirt @bitwarden/team-data-insights-and-reporting-dev +libs/dirt @bitwarden/team-data-insights-and-reporting-dev ## Localization/Crowdin (Platform and Tools team) apps/browser/src/_locales @bitwarden/team-tools-dev @bitwarden/team-platform-dev diff --git a/.storybook/main.ts b/.storybook/main.ts index d5d116e99be..879e87fe376 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -8,6 +8,8 @@ const config: StorybookConfig = { stories: [ "../libs/auth/src/**/*.mdx", "../libs/auth/src/**/*.stories.@(js|jsx|ts|tsx)", + "../libs/dirt/card/src/**/*.mdx", + "../libs/dirt/card/src/**/*.stories.@(js|jsx|ts|tsx)", "../libs/tools/send/send-ui/src/**/*.mdx", "../libs/tools/send/send-ui/src/**/*.stories.@(js|jsx|ts|tsx)", "../libs/vault/src/**/*.mdx", @@ -20,8 +22,6 @@ const config: StorybookConfig = { "../apps/browser/src/**/*.stories.@(js|jsx|ts|tsx)", "../bitwarden_license/bit-web/src/**/*.mdx", "../bitwarden_license/bit-web/src/**/*.stories.@(js|jsx|ts|tsx)", - "../libs/tools/card/src/**/*.mdx", - "../libs/tools/card/src/**/*.stories.@(js|jsx|ts|tsx)", "../libs/angular/src/**/*.stories.@(js|jsx|ts|tsx)", ], addons: [ diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index e24985f58af..ff4ac340219 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -19,6 +19,7 @@ "@bitwarden/billing": ["../../libs/billing/src"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], + "@bitwarden/dirt-card": ["../../libs/dirt/card/src"], "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], @@ -31,7 +32,6 @@ "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/platform/*": ["../../libs/platform/src/*"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": ["../../libs/tools/card/src"], "@bitwarden/ui-common": ["../../libs/ui/common/src"], "@bitwarden/ui-common/setup-jest": ["../../libs/ui/common/src/setup-jest"], "@bitwarden/vault-export-core": [ diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index 78b3512405e..27e38757e97 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -17,6 +17,7 @@ "@bitwarden/billing": ["../../libs/billing/src"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], + "@bitwarden/dirt-card": ["../../libs/dirt/card/src"], "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], @@ -29,7 +30,6 @@ "@bitwarden/node/*": ["../../libs/node/src/*"], "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": ["../../libs/tools/card/src"], "@bitwarden/ui-common": ["../../libs/ui/common/src"], "@bitwarden/ui-common/setup-jest": ["../../libs/ui/common/src/setup-jest"], "@bitwarden/vault-export-core": [ diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 3d62a30bc01..0cc988ea722 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -13,6 +13,7 @@ "@bitwarden/bit-common/*": ["../../bitwarden_license/bit-common/src/*"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], + "@bitwarden/dirt-card": ["../../libs/dirt/card/src"], "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], @@ -24,7 +25,6 @@ "@bitwarden/key-management-ui": ["../../libs/key-management-ui/src"], "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": ["../../libs/tools/card/src"], "@bitwarden/ui-common": ["../../libs/ui/common/src"], "@bitwarden/ui-common/setup-jest": ["../../libs/ui/common/src/setup-jest"], "@bitwarden/vault-export-core": [ diff --git a/bitwarden_license/bit-common/tsconfig.json b/bitwarden_license/bit-common/tsconfig.json index 641b0ac6aa9..c92167bb919 100644 --- a/bitwarden_license/bit-common/tsconfig.json +++ b/bitwarden_license/bit-common/tsconfig.json @@ -12,6 +12,7 @@ "@bitwarden/bit-common/*": ["../bit-common/src/*"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], + "@bitwarden/dirt-card": ["../../libs/dirt/card/src"], "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], @@ -20,7 +21,6 @@ "@bitwarden/key-management": ["../../libs/key-management/src"], "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": ["../../libs/tools/card/src"], "@bitwarden/ui-common": ["../../libs/ui/common/src"], "@bitwarden/ui-common/setup-jest": ["../../libs/ui/common/src/setup-jest"], "@bitwarden/vault-export-core": [ diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html index 6f8e738fdc3..0dfe55bed48 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html @@ -26,7 +26,7 @@

{{ "allApplications" | i18n }}

- - - + - +
- - - + - +
({ props: args, template: /*html*/ ` - `, + `, }), }; diff --git a/libs/tools/card/src/index.ts b/libs/dirt/card/src/index.ts similarity index 100% rename from libs/tools/card/src/index.ts rename to libs/dirt/card/src/index.ts diff --git a/libs/tools/card/test.setup.ts b/libs/dirt/card/test.setup.ts similarity index 100% rename from libs/tools/card/test.setup.ts rename to libs/dirt/card/test.setup.ts diff --git a/libs/tools/card/tsconfig.json b/libs/dirt/card/tsconfig.json similarity index 100% rename from libs/tools/card/tsconfig.json rename to libs/dirt/card/tsconfig.json diff --git a/libs/tools/card/tsconfig.spec.json b/libs/dirt/card/tsconfig.spec.json similarity index 100% rename from libs/tools/card/tsconfig.spec.json rename to libs/dirt/card/tsconfig.spec.json diff --git a/libs/shared/tsconfig.spec.json b/libs/shared/tsconfig.spec.json index 6d2c7498129..5402594e5ab 100644 --- a/libs/shared/tsconfig.spec.json +++ b/libs/shared/tsconfig.spec.json @@ -10,6 +10,7 @@ "@bitwarden/billing": ["../billing/src"], "@bitwarden/common/*": ["../common/src/*"], "@bitwarden/components": ["../components/src"], + "@bitwarden/dirt-card": ["../dirt/card/src"], "@bitwarden/generator-components": ["../tools/generator/components/src"], "@bitwarden/generator-core": ["../tools/generator/core/src"], "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], @@ -22,7 +23,6 @@ "@bitwarden/node/*": ["../node/src/*"], "@bitwarden/platform": ["../platform/src"], "@bitwarden/send-ui": ["../tools/send/send-ui/src"], - "@bitwarden/tools-card": ["../tools/card/src"], "@bitwarden/ui-common": ["../ui/common/src"], "@bitwarden/ui-common/setup-jest": ["../ui/common/src/setup-jest"], "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"], diff --git a/libs/tools/card/README.md b/libs/tools/card/README.md deleted file mode 100644 index 5e28e62d154..00000000000 --- a/libs/tools/card/README.md +++ /dev/null @@ -1,5 +0,0 @@ -## Tools Card - -Package name: `@bitwarden/tools-card` - -Generic Tools Card Component diff --git a/package-lock.json b/package-lock.json index 071f42c94c9..d4009fff06d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -277,6 +277,11 @@ "name": "@bitwarden/components", "version": "0.0.0" }, + "libs/dirt/card": { + "name": "@bitwarden/dirt-card", + "version": "0.0.0", + "license": "GPL-3.0" + }, "libs/importer": { "name": "@bitwarden/importer", "version": "0.0.0", @@ -302,11 +307,6 @@ "version": "0.0.0", "license": "GPL-3.0" }, - "libs/tools/card": { - "name": "@bitwarden/tools-card", - "version": "0.0.0", - "license": "GPL-3.0" - }, "libs/tools/export/vault-export/vault-export-core": { "name": "@bitwarden/vault-export-core", "version": "0.0.0", @@ -5169,6 +5169,10 @@ "resolved": "apps/desktop/desktop_native/napi", "link": true }, + "node_modules/@bitwarden/dirt-card": { + "resolved": "libs/dirt/card", + "link": true + }, "node_modules/@bitwarden/generator-components": { "resolved": "libs/tools/generator/components", "link": true @@ -5219,10 +5223,6 @@ "resolved": "libs/tools/send/send-ui", "link": true }, - "node_modules/@bitwarden/tools-card": { - "resolved": "libs/tools/card", - "link": true - }, "node_modules/@bitwarden/ui-common": { "resolved": "libs/ui/common", "link": true diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index e8c3f669c0c..90b95ff54bf 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -23,6 +23,7 @@ "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"], "@bitwarden/common/*": ["./libs/common/src/*"], "@bitwarden/components": ["./libs/components/src"], + "@bitwarden/dirt-card": [".libs/dirt/card/src"], "@bitwarden/generator-components": ["./libs/tools/generator/components/src"], "@bitwarden/generator-core": ["./libs/tools/generator/core/src"], "@bitwarden/generator-history": ["./libs/tools/generator/extensions/history/src"], @@ -35,7 +36,6 @@ "@bitwarden/node/*": ["./libs/node/src/*"], "@bitwarden/platform": ["./libs/platform/src"], "@bitwarden/send-ui": [".libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": [".libs/tools/card/src"], "@bitwarden/ui-common": ["./libs/ui/common/src"], "@bitwarden/vault-export-core": [".libs/tools/export/vault-export/vault-export-core/src"], "@bitwarden/vault-export-ui": [".libs/tools/export/vault-export/vault-export-ui/src"], diff --git a/tsconfig.json b/tsconfig.json index c82851d50c8..525af0ac3b7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,6 +24,7 @@ "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"], "@bitwarden/common/*": ["./libs/common/src/*"], "@bitwarden/components": ["./libs/components/src"], + "@bitwarden/dirt-card": ["./libs/dirt/card/src"], "@bitwarden/generator-components": ["./libs/tools/generator/components/src"], "@bitwarden/generator-core": ["./libs/tools/generator/core/src"], "@bitwarden/generator-history": ["./libs/tools/generator/extensions/history/src"], @@ -37,7 +38,6 @@ "@bitwarden/platform": ["./libs/platform/src"], "@bitwarden/platform/*": ["./libs/platform/src/*"], "@bitwarden/send-ui": ["./libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": ["./libs/tools/card/src"], "@bitwarden/ui-common": ["./libs/ui/common/src"], "@bitwarden/ui-common/setup-jest": ["./libs/ui/common/src/setup-jest"], "@bitwarden/vault-export-core": ["./libs/tools/export/vault-export/vault-export-core/src"], @@ -57,7 +57,7 @@ "apps/browser/src/**/*", "libs/*/src/**/*", "libs/tools/send/**/src/**/*", - "libs/tools/card/src/**/*", + "libs/dirt/card/src/**/*", "bitwarden_license/bit-web/src/**/*", "bitwarden_license/bit-common/src/**/*" ], From da9aa07e4bde28fb6ec13d98417c64381a78501d Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 10:08:28 +0200 Subject: [PATCH 188/245] Autosync the updated translations (#14996) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/af/messages.json | 64 ++++++-- apps/desktop/src/locales/ar/messages.json | 64 ++++++-- apps/desktop/src/locales/az/messages.json | 66 ++++++-- apps/desktop/src/locales/be/messages.json | 64 ++++++-- apps/desktop/src/locales/bg/messages.json | 66 ++++++-- apps/desktop/src/locales/bn/messages.json | 64 ++++++-- apps/desktop/src/locales/bs/messages.json | 64 ++++++-- apps/desktop/src/locales/ca/messages.json | 64 ++++++-- apps/desktop/src/locales/cs/messages.json | 64 ++++++-- apps/desktop/src/locales/cy/messages.json | 64 ++++++-- apps/desktop/src/locales/da/messages.json | 64 ++++++-- apps/desktop/src/locales/de/messages.json | 66 ++++++-- apps/desktop/src/locales/el/messages.json | 64 ++++++-- apps/desktop/src/locales/en_GB/messages.json | 64 ++++++-- apps/desktop/src/locales/en_IN/messages.json | 64 ++++++-- apps/desktop/src/locales/eo/messages.json | 162 ++++++++++++------- apps/desktop/src/locales/es/messages.json | 64 ++++++-- apps/desktop/src/locales/et/messages.json | 64 ++++++-- apps/desktop/src/locales/eu/messages.json | 64 ++++++-- apps/desktop/src/locales/fa/messages.json | 66 ++++++-- apps/desktop/src/locales/fi/messages.json | 64 ++++++-- apps/desktop/src/locales/fil/messages.json | 64 ++++++-- apps/desktop/src/locales/fr/messages.json | 64 ++++++-- apps/desktop/src/locales/gl/messages.json | 64 ++++++-- apps/desktop/src/locales/he/messages.json | 64 ++++++-- apps/desktop/src/locales/hi/messages.json | 64 ++++++-- apps/desktop/src/locales/hr/messages.json | 64 ++++++-- apps/desktop/src/locales/hu/messages.json | 64 ++++++-- apps/desktop/src/locales/id/messages.json | 64 ++++++-- apps/desktop/src/locales/it/messages.json | 64 ++++++-- apps/desktop/src/locales/ja/messages.json | 64 ++++++-- apps/desktop/src/locales/ka/messages.json | 64 ++++++-- apps/desktop/src/locales/km/messages.json | 64 ++++++-- apps/desktop/src/locales/kn/messages.json | 64 ++++++-- apps/desktop/src/locales/ko/messages.json | 64 ++++++-- apps/desktop/src/locales/lt/messages.json | 64 ++++++-- apps/desktop/src/locales/lv/messages.json | 66 ++++++-- apps/desktop/src/locales/me/messages.json | 64 ++++++-- apps/desktop/src/locales/ml/messages.json | 64 ++++++-- apps/desktop/src/locales/mr/messages.json | 64 ++++++-- apps/desktop/src/locales/my/messages.json | 64 ++++++-- apps/desktop/src/locales/nb/messages.json | 64 ++++++-- apps/desktop/src/locales/ne/messages.json | 64 ++++++-- apps/desktop/src/locales/nl/messages.json | 64 ++++++-- apps/desktop/src/locales/nn/messages.json | 64 ++++++-- apps/desktop/src/locales/or/messages.json | 64 ++++++-- apps/desktop/src/locales/pl/messages.json | 64 ++++++-- apps/desktop/src/locales/pt_BR/messages.json | 64 ++++++-- apps/desktop/src/locales/pt_PT/messages.json | 64 ++++++-- apps/desktop/src/locales/ro/messages.json | 64 ++++++-- apps/desktop/src/locales/ru/messages.json | 64 ++++++-- apps/desktop/src/locales/si/messages.json | 64 ++++++-- apps/desktop/src/locales/sk/messages.json | 64 ++++++-- apps/desktop/src/locales/sl/messages.json | 64 ++++++-- apps/desktop/src/locales/sr/messages.json | 66 ++++++-- apps/desktop/src/locales/sv/messages.json | 64 ++++++-- apps/desktop/src/locales/te/messages.json | 64 ++++++-- apps/desktop/src/locales/th/messages.json | 64 ++++++-- apps/desktop/src/locales/tr/messages.json | 64 ++++++-- apps/desktop/src/locales/uk/messages.json | 78 ++++++--- apps/desktop/src/locales/vi/messages.json | 64 ++++++-- apps/desktop/src/locales/zh_CN/messages.json | 72 +++++++-- apps/desktop/src/locales/zh_TW/messages.json | 64 ++++++-- 63 files changed, 3342 insertions(+), 822 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 28cff8cae13..2467eb0194a 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Ander" }, - "generatePassword": { - "message": "Wek 'n Wagwoord op" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Tipe" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Wek 'n Wagwoord op" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Lukraak" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Swak wagwoord geidentifiseer en gevind in 'n data lekkasie. Gebruik 'n sterk en unieke wagwoord om jou rekening te beskerm. Is jy seker dat jy hierdie wagwoord wil gebruik?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Kontroleer bekende data lekkasies vir hierdie wagwoord" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index d9dfb7cdff5..7bd410330a6 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -351,12 +351,6 @@ "other": { "message": "أخرى" }, - "generatePassword": { - "message": "توليد كلمة المرور" - }, - "generatePassphrase": { - "message": "توليد عبارة المرور" - }, "type": { "message": "نوع" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "توليد كلمة المرور" + }, + "generatePassphrase": { + "message": "توليد عبارة المرور" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "يجب أن تكون القيمة بين $MIN$ و $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "عشوائي" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "كلمة مرور ضعيفة محددة وموجودة في خرق البيانات. استخدم كلمة مرور قوية وفريدة لحماية حسابك. هل أنت متأكد من أنك تريد استخدام كلمة المرور هذه؟" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "تحقق من خروقات البيانات المعروفة لكلمة المرور هذه" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 5c1f49cbfff..5e882c8f3cc 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Digər" }, - "generatePassword": { - "message": "Parol yarat" - }, - "generatePassphrase": { - "message": "Keçid ifadələri yarat" - }, "type": { "message": "Növ" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "İstifadəçi adı yaradıcı" }, + "generatePassword": { + "message": "Parol yarat" + }, + "generatePassphrase": { + "message": "Keçid ifadələri yarat" + }, + "passwordGenerated": { + "message": "Parol yaradıldı" + }, + "passphraseGenerated": { + "message": "Keçid ifadəsi yaradıldı" + }, + "usernameGenerated": { + "message": "İstifadəçi adı yaradıldı" + }, + "emailGenerated": { + "message": "E-poçt yaradıldı" + }, "spinboxBoundariesHint": { "message": "Dəyər, $MIN$-$MAX$ arasında olmalıdır.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Bu e-poçtu istifadə et" }, + "useThisPassword": { + "message": "Bu parolu istifadə et" + }, + "useThisPassphrase": { + "message": "Bu keçid ifadəsini istifadə et" + }, + "useThisUsername": { + "message": "Bu istifadəçi adını istifadə et" + }, "random": { "message": "Təsadüfi" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Zəif parol məlumat pozuntusunda aşkarlandı və tapıldı. Hesabınızı qorumaq üçün güclü və unikal bir parol istifadə edin. Bu parolu istifadə etmək istədiyinizə əminsiniz?" }, - "useThisPassword": { - "message": "Bu parolu istifadə et" - }, - "useThisUsername": { - "message": "Bu istifadəçi adını istifadə et" - }, "checkForBreaches": { "message": "Bu parol üçün bilinən məlumat pozuntularını yoxlayın" }, @@ -3704,7 +3719,7 @@ "message": "Riskli parolları dəyişdir" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "\"Yalnız baxma\" icazələrinə sahib kolleksiyaları silə bilməzsiniz: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Ana qovluğun adından sonra \"/\" əlavə edərək qovluğu ardıcıl yerləşdirin. Nümunə: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send, həssas məlumatlar təhlükəsizdir", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "İstənilən platformada faylları və dataları hər kəslə paylaşın. İfşa olunmağı məhdudlaşdıraraq məlumatlarınız ucdan-uca şifrələnmiş qalacaq.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Cəld parol yaradın" + }, + "generatorNudgeBodyOne": { + "message": "Klikləyərək güclü və unikal parolları asanlıqla yaradın", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "və girişlərinizi güvənli şəkildə saxlayın.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Girişlərinizi güvənli şəkildə saxlamağınıza kömək etməsi üçün Parol yarat düyməsinə klikləyərək güclü və unikal parolları asanlıqla yaradın.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Avto-doldurma ilə vaxta qənaət edin" }, diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 9284f683e58..0192ae8977c 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Iншае" }, - "generatePassword": { - "message": "Генерыраваць пароль" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Тып" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Генерыраваць пароль" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Выпадкова" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Вызначаны ненадзейны пароль, які знойдзены ва ўцечках даных. Выкарыстоўвайце надзейныя і ўнікальныя паролі для абароны свайго ўліковага запісу. Вы сапраўды хочаце выкарыстоўваць гэты пароль?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Праверыць у вядомых уцечках даных для гэтага пароля" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 18a1d8cf2f0..5ea63c8ce79 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Други" }, - "generatePassword": { - "message": "Нова парола" - }, - "generatePassphrase": { - "message": "Генериране на парола-фраза" - }, "type": { "message": "Вид" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Генератор на потребителски имена" }, + "generatePassword": { + "message": "Нова парола" + }, + "generatePassphrase": { + "message": "Генериране на парола-фраза" + }, + "passwordGenerated": { + "message": "Паролата е генерирана" + }, + "passphraseGenerated": { + "message": "Паролата-фраза е генерирана" + }, + "usernameGenerated": { + "message": "Потребителското име е генерирано" + }, + "emailGenerated": { + "message": "Е-пощата е генерирана" + }, "spinboxBoundariesHint": { "message": "Стойността трябва да бъде между $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Използване на тази е-поща" }, + "useThisPassword": { + "message": "Използване на тази парола" + }, + "useThisPassphrase": { + "message": "Използване на тази парола-фраза" + }, + "useThisUsername": { + "message": "Използване на това потребителско име" + }, "random": { "message": "Произволно" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Разпозната е слаба парола. Използвайте силна парола, за да защитете данните си. Наистина ли искате да използвате слаба парола?" }, - "useThisPassword": { - "message": "Използване на тази парола" - }, - "useThisUsername": { - "message": "Използване на това потребителско име" - }, "checkForBreaches": { "message": "Проверяване в известните случаи на изтекли данни за тази парола" }, @@ -3704,7 +3719,7 @@ "message": "Промяна на парола в риск" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "Не можете да премахвате колекции с права „Само за преглед“: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Можете да вложите една папка в друга като въведете името на горната папка, а след това „/“. Пример: Социални/Форуми" }, + "sendsTitleNoItems": { + "message": "Изпращайте чувствителна информация сигурно", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Споделяйте сигурно файлове и данни с всекиго, през всяка система. Информацията Ви ще бъде защитена с шифроване от край до край, а видимостта ѝ ще бъде ограничена.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Създавайте пароли бързо" + }, + "generatorNudgeBodyOne": { + "message": "Създавайте лесно сложни и уникални пароли като щракнете върху", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "за да защитите данните си за вписване.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Създавайте лесно сложни и уникални пароли като щракнете върху бутона за генериране на парола, за да защитите данните си за вписване.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Спестете време с автоматично попълване" }, diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index b3a2b6e21ee..0626b7c5496 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -351,12 +351,6 @@ "other": { "message": "অন্যান্য" }, - "generatePassword": { - "message": "পাসওয়ার্ড তৈরি করুন" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "ধরন" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "পাসওয়ার্ড তৈরি করুন" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 89015368c11..44b6704a4a2 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Ostalo" }, - "generatePassword": { - "message": "Generiraj lozinku" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Vrsta" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generiraj lozinku" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 5550e2d0c62..65c766dc3f7 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Altres" }, - "generatePassword": { - "message": "Genera contrasenya" - }, - "generatePassphrase": { - "message": "Genera frase de pas" - }, "type": { "message": "Tipus" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Genera contrasenya" + }, + "generatePassphrase": { + "message": "Genera frase de pas" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "El valor ha d'estar entre $MIN$ i $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Utilitza aquest correu" }, + "useThisPassword": { + "message": "Utilitzeu aquesta contrasenya" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Utilitzeu aquest nom d'usuari" + }, "random": { "message": "Aleatori" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Contrasenya feble identificada i trobada en una filtració de dades. Utilitzeu una contrasenya única i segura per protegir el vostre compte. Esteu segur que voleu utilitzar aquesta contrasenya?" }, - "useThisPassword": { - "message": "Utilitzeu aquesta contrasenya" - }, - "useThisUsername": { - "message": "Utilitzeu aquest nom d'usuari" - }, "checkForBreaches": { "message": "Comproveu les filtracions de dades conegudes per a aquesta contrasenya" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index b6c3edd783a..2e0a8b7db57 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Ostatní" }, - "generatePassword": { - "message": "Vygenerovat heslo" - }, - "generatePassphrase": { - "message": "Vygenerovat heslovou frázi" - }, "type": { "message": "Typ" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Generátor uživatelského jména" }, + "generatePassword": { + "message": "Vygenerovat heslo" + }, + "generatePassphrase": { + "message": "Vygenerovat heslovou frázi" + }, + "passwordGenerated": { + "message": "Heslo bylo vygenerováno" + }, + "passphraseGenerated": { + "message": "Heslová fráze byla vygenerována" + }, + "usernameGenerated": { + "message": "Uživatelské jméno bylo vygenerováno" + }, + "emailGenerated": { + "message": "E-mail byl vygenerován" + }, "spinboxBoundariesHint": { "message": "Hodnota musí být mezi $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Použít tento e-mail" }, + "useThisPassword": { + "message": "Použít toto heslo" + }, + "useThisPassphrase": { + "message": "Použít tuto heslovou frázi" + }, + "useThisUsername": { + "message": "Použít toto uživatelské jméno" + }, "random": { "message": "Náhodný" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Slabé heslo bylo nalezeno mezi odhalenými hesly. K zabezpečení Vašeho účtu používejte silné a jedinečné heslo. Opravdu chcete používat toto heslo?" }, - "useThisPassword": { - "message": "Použít toto heslo" - }, - "useThisUsername": { - "message": "Použít toto uživatelské jméno" - }, "checkForBreaches": { "message": "Zkontrolovat heslo, zda nebylo odhaleno" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Vnořte složku přidáním názvu nadřazené složky následovaného znakem \"/\". Příklad: Sociální/Fóra" }, + "sendsTitleNoItems": { + "message": "Posílejte citlivé informace bezpečně", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Sdílejte bezpečně soubory a data s kýmkoli na libovolné platformě. Vaše informace zůstanou šifrovány a zároveň omezují expozici.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Rychlé vytvoření hesla" + }, + "generatorNudgeBodyOne": { + "message": "Jednoduše vytvořte silná a unikátní hesla klepnutím na", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "aby Vám pomohlo udržet Vaše přihlašovací údaje v bezpečí.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Jednoduše vytvořte silná a unikátní hesla klepnutím na tlačítko Generovat heslo, které Vám pomůže udržet Vaše přihlašovací údaje v bezpečí.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Ušetřete čas s automatickým vyplňováním" }, diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 28ed661423d..b16311ac05a 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Other" }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index f547eea69a0..1227798ed7e 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Andre" }, - "generatePassword": { - "message": "Generér adgangskode" - }, - "generatePassphrase": { - "message": "Generér adgangssætning" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generér adgangskode" + }, + "generatePassphrase": { + "message": "Generér adgangssætning" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Værdi skal være mellem $MIN$ og $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Anvend denne adgangskode" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Anvend dette brugernavn" + }, "random": { "message": "Tilfældig" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Svag adgangskode identificeret og fundet i datalæk. Brug en unik adgangskode til at beskytte din konto. Sikker på, at at denne adgangskode skal bruges?" }, - "useThisPassword": { - "message": "Anvend denne adgangskode" - }, - "useThisUsername": { - "message": "Anvend dette brugernavn" - }, "checkForBreaches": { "message": "Tjek kendte datalæk for denne adgangskode" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index de39eba6f99..29a98d66ba6 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Sonstige" }, - "generatePassword": { - "message": "Passwort generieren" - }, - "generatePassphrase": { - "message": "Passphrase generieren" - }, "type": { "message": "Typ" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Benutzernamen-Generator" }, + "generatePassword": { + "message": "Passwort generieren" + }, + "generatePassphrase": { + "message": "Passphrase generieren" + }, + "passwordGenerated": { + "message": "Passwort generiert" + }, + "passphraseGenerated": { + "message": "Passphrase generiert" + }, + "usernameGenerated": { + "message": "Benutzername generiert" + }, + "emailGenerated": { + "message": "E-Mail-Adresse generiert" + }, "spinboxBoundariesHint": { "message": "Wert muss zwischen $MIN$ und $MAX$ liegen.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Diese E-Mail-Adresse verwenden" }, + "useThisPassword": { + "message": "Dieses Passwort verwenden" + }, + "useThisPassphrase": { + "message": "Diese Passphrase verwenden" + }, + "useThisUsername": { + "message": "Diesen Benutzernamen verwenden" + }, "random": { "message": "Zufällig" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Schwaches Passwort erkannt und in einem Datendiebstahl gefunden. Verwende ein starkes und einzigartiges Passwort, um dein Konto zu schützen. Bist du sicher, dass du dieses Passwort verwenden möchtest?" }, - "useThisPassword": { - "message": "Dieses Passwort verwenden" - }, - "useThisUsername": { - "message": "Diesen Benutzernamen verwenden" - }, "checkForBreaches": { "message": "Bekannte Datendiebstähle auf dieses Passwort überprüfen" }, @@ -3704,7 +3719,7 @@ "message": "Gefährdetes Passwort ändern" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "Du kannst Sammlungen mit Leseberechtigung nicht entfernen: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Verschachtel einen Ordner, indem du den Namen des übergeordneten Ordners hinzufügst, gefolgt von einem „/“. Beispiel: Sozial/Foren" }, + "sendsTitleNoItems": { + "message": "Sensible Informationen sicher versenden", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Teile Dateien und Daten sicher mit jedem auf jeder Plattform. Deine Informationen bleiben Ende-zu-Ende-verschlüsselt, während die Verbreitung begrenzt wird.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Passwörter schnell erstellen" + }, + "generatorNudgeBodyOne": { + "message": "Generiere ganz einfach starke und einzigartige Passwörter, indem du auf den", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": ", um dir zu helfen, deine Zugangsdaten sicher zu halten.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Generiere ganz einfach starke und einzigartige Passwörter, indem du auf den \"Passwort generieren\"-Button klickst, um dir zu helfen, deine Zugangsdaten sicher zu halten.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Spare Zeit mit Auto-Ausfüllen" }, diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 3133488f805..c77ba102135 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Άλλες" }, - "generatePassword": { - "message": "Γέννηση κωδικού πρόσβασης" - }, - "generatePassphrase": { - "message": "Δημιουργία φράσης πρόσβασης" - }, "type": { "message": "Τύπος" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Γέννηση κωδικού πρόσβασης" + }, + "generatePassphrase": { + "message": "Δημιουργία φράσης πρόσβασης" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Η τιμή πρέπει να είναι μεταξύ $MIN$ και $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Χρήση αυτού του email" }, + "useThisPassword": { + "message": "Χρήση αυτού του κωδικού πρόσβασης" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Χρήση αυτού του ονόματος χρήστη" + }, "random": { "message": "Τυχαίο" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Βρέθηκε και ταυτοποιήθηκε αδύναμος κωδικός σε μια διαρροή δεδομένων. Χρησιμοποιήστε ένα ισχυρό και μοναδικό κωδικό πρόσβασης για την προστασία του λογαριασμού σας. Είστε σίγουροι ότι θέλετε να χρησιμοποιήσετε αυτόν τον κωδικό πρόσβασης;" }, - "useThisPassword": { - "message": "Χρήση αυτού του κωδικού πρόσβασης" - }, - "useThisUsername": { - "message": "Χρήση αυτού του ονόματος χρήστη" - }, "checkForBreaches": { "message": "Ελέγξτε γνωστές διαρροές δεδομένων για αυτόν τον κωδικό" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index cf4d0986e1f..b2166468380 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Other" }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index d82cb566a98..153387f7b00 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Other" }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 8e8cc09341f..7ebfc4955e9 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -27,7 +27,7 @@ "message": "Sekura noto" }, "typeSshKey": { - "message": "SSH key" + "message": "SSH-ŝlosilo" }, "folders": { "message": "Dosierujoj" @@ -64,7 +64,7 @@ } }, "welcomeBack": { - "message": "Welcome back" + "message": "Bonrevenon!" }, "moveToOrgDesc": { "message": "Elektu organizaĵon, al kiu vi volas movi ĉi tiun eron. Movado al organizaĵo transdonas la posedon de la ero al tiu organizaĵo. Vi ne plu estos la rekta posedanto de la ero post kiam ĝi estos movita." @@ -351,12 +351,6 @@ "other": { "message": "Alia" }, - "generatePassword": { - "message": "Generi pasvorton" - }, - "generatePassphrase": { - "message": "Generi pasfrazon" - }, "type": { "message": "Tipo" }, @@ -421,7 +415,7 @@ "message": "Website (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "Retejo (URI) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -431,7 +425,7 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "Retejo aldoniĝos" }, "addWebsite": { "message": "Add website" @@ -440,10 +434,10 @@ "message": "Delete website" }, "owner": { - "message": "Owner" + "message": "Posedanto" }, "addField": { - "message": "Add field" + "message": "Aldoni kampon" }, "editField": { "message": "Edit field" @@ -551,10 +545,10 @@ "message": "Add folder" }, "editFolder": { - "message": "Edit folder" + "message": "Redakti la dosierujon" }, "regeneratePassword": { - "message": "Regenerate password" + "message": "Regeneri la pasvorton" }, "copyPassword": { "message": "Copy password" @@ -566,7 +560,7 @@ "message": "Copy SSH private key" }, "copyPassphrase": { - "message": "Copy passphrase", + "message": "Kopii la pasfrazon", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -670,7 +664,7 @@ "message": "Dosierujo por serĉi" }, "searchFavorites": { - "message": "Search favorites" + "message": "Serĉi favoratojn" }, "searchType": { "message": "Tipo por serĉi", @@ -734,7 +728,7 @@ "message": "Saluti en Bitwarden'on" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "Enmetu la kodon senditan al via retpoŝto" }, "enterTheCodeFromYourAuthenticatorApp": { "message": "Enter the code from your authenticator app" @@ -761,7 +755,7 @@ "message": "The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it." }, "masterPassHintDesc": { - "message": "A master password hint can help you remember your password if you forget it." + "message": "Aludo de ĉefa pasvorto povas helpi vin rememorigi vian pasvorton, se vi forgesas ĝin." }, "reTypeMasterPass": { "message": "Re-entajpu la ĉefan pasvorton" @@ -792,7 +786,7 @@ "message": "Konfirmi la ĉefan pasvorton" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Aludo de la ĉefa pasvorto" }, "passwordStrengthScore": { "message": "Password strength score $SCORE$", @@ -837,19 +831,19 @@ "message": "Get master password hint" }, "emailRequired": { - "message": "Email address is required." + "message": "Retpoŝtadreso estas postulata." }, "invalidEmail": { "message": "Invalid email address." }, "masterPasswordRequired": { - "message": "Master password is required." + "message": "Ĉefa pasvorto estas postulata" }, "confirmMasterPasswordRequired": { - "message": "Master password retype is required." + "message": "Reentajpi la ĉefan pasvorton estas postulata" }, "masterPasswordMinlength": { - "message": "Master password must be at least $VALUE$ characters long.", + "message": "La ĉefa pasvorto devas esti apenaŭ $VALUE$ signojn longa.", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -868,7 +862,7 @@ "message": "Master password confirmation does not match." }, "newAccountCreated": { - "message": "Via nova konto estas kreita! Vi eble ĵus salutis," + "message": "Via nova konto estas kreita! Vi eble nun salutas," }, "newAccountCreated2": { "message": "Via nova konto estas kreita!" @@ -877,13 +871,13 @@ "message": "Vi estas en salutaĵo!" }, "masterPassSent": { - "message": "We've sent you an email with your master password hint." + "message": "Ni sendis al vi retleteron kun la aludo de via ĉefa pasvorto." }, "unexpectedError": { "message": "An unexpected error has occurred." }, "itemInformation": { - "message": "Item information" + "message": "Informo de ero" }, "noItemsInList": { "message": "There are no items to list." @@ -919,7 +913,7 @@ "message": "Daŭrigi" }, "verificationCodeEmailSent": { - "message": "Verification email sent to $EMAIL$.", + "message": "Konfirmiga retletero sendiĝis al $EMAIL$.", "placeholders": { "email": { "content": "$1", @@ -928,7 +922,7 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "Ne demandu denove ĉe tiu ĉi aparato por 30 tagoj" }, "selectAnotherMethod": { "message": "Elekti alian metodon", @@ -947,10 +941,10 @@ "message": "Restariga kodo" }, "authenticatorAppTitle": { - "message": "Authenticator app" + "message": "Aŭtentiga apo" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "Enmetu kodon generitan de aŭtentiga apo, kiel Bitwarden-Aŭtentigilo.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { @@ -960,7 +954,7 @@ "message": "Use a YubiKey to access your account. Works with YubiKey 4, 4 Nano, 4C, and NEO devices." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "Enmetu kodon generitan de Duo Security.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -983,13 +977,13 @@ "message": "Use any WebAuthn compatible security key to access your account." }, "emailTitle": { - "message": "Email" + "message": "Retpoŝto" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Enmetu kodon sendiĝis al via retpoŝto." }, "loginUnavailable": { - "message": "Login unavailable" + "message": "Saluto nedisponeblas" }, "noTwoStepProviders": { "message": "This account has two-step login set up, however, none of the configured two-step providers are supported by this device." @@ -1032,7 +1026,7 @@ "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { - "message": "API server URL" + "message": "URL de API-servilo" }, "webVaultUrl": { "message": "Web vault server URL" @@ -1140,13 +1134,13 @@ "message": "Samhavigi la trezorujon" }, "changeMasterPass": { - "message": "Change master password" + "message": "Ŝanĝi la ĉefan pasvorton" }, "continueToWebApp": { "message": "Daŭrigu en la retumilan apon?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "Vi povas ŝanĝi vian ĉefan pasvorton en la retumila apo de Bitwarden" + "message": "Vi povas ŝanĝi vian ĉefan pasvorton en la reteja apo de Bitwarden" }, "fingerprintPhrase": { "message": "Fingerprint phrase", @@ -1175,7 +1169,7 @@ "message": "Via trezorejo estas ŝlosita. Kontrolu vian identecon por daŭrigi." }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Via konto estas ŝlosita" }, "or": { "message": "aŭ" @@ -1184,7 +1178,7 @@ "message": "Unlock with biometrics" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Malŝlosi per la ĉefa pasvorto" }, "unlock": { "message": "Malŝlosi" @@ -1209,7 +1203,7 @@ "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" }, "twoStepLogin": { - "message": "Two-step login" + "message": "Duŝtupa salutado" }, "vaultTimeout": { "message": "Vault timeout" @@ -1263,7 +1257,7 @@ "message": "On system lock" }, "onRestart": { - "message": "On restart" + "message": "Ĉe relanĉo" }, "never": { "message": "Neniam" @@ -1394,7 +1388,7 @@ } }, "updateAvailable": { - "message": "Update available" + "message": "Disponeblas ĝisdatigo" }, "updateAvailableDesc": { "message": "An update was found. Do you want to download it now?" @@ -1546,7 +1540,7 @@ "message": "Toggle full screen" }, "reload": { - "message": "Reload" + "message": "Reŝargi" }, "toggleDevTools": { "message": "Toggle developer tools" @@ -1572,7 +1566,7 @@ "message": "Hide Bitwarden" }, "hideOthers": { - "message": "Hide others" + "message": "Kaŝi la aliajn" }, "showAll": { "message": "Montri ĉiujn" @@ -1675,7 +1669,7 @@ "description": "Application window should always stay on top of other windows" }, "dateUpdated": { - "message": "Updated", + "message": "Ĝisdatigita", "description": "ex. Date this item was updated" }, "dateCreated": { @@ -1683,11 +1677,11 @@ "description": "ex. Date this item was created" }, "datePasswordUpdated": { - "message": "Password updated", + "message": "Pasvorto ĝisdatiĝis", "description": "ex. Date this password was updated" }, "exportFrom": { - "message": "Export from" + "message": "Elporti el" }, "exportVault": { "message": "Export vault" @@ -1696,31 +1690,31 @@ "message": "File format" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "Tiu ĉi dosieriga elporto estos pasvorte protektata kaj postulas la pasvorton de la dosiero por malĉifri." }, "filePassword": { - "message": "File password" + "message": "Pasvorto de la dosiero" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "Tiu pasvorto estos uzata por elporti kaj enporti tiun dosieron" }, "accountRestrictedOptionDescription": { "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." }, "passwordProtected": { - "message": "Password protected" + "message": "Pasvorte protektata" }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "Ŝargi pasvorton al la dosiero por ĉifri la elporton kaj ĝin enporti al ajna konto ĉe Bitwarden uzante la pasvorton por malĉifri." }, "exportTypeHeading": { - "message": "Export type" + "message": "Tipo de elporto" }, "accountRestricted": { "message": "Account restricted" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "Ne akordas la «Pasvorto de la dosiero» kaj «Konfirmu la pasvorton de la dosiero»." }, "done": { "message": "Preta" @@ -2134,7 +2128,7 @@ "message": "Use hardware acceleration" }, "enableHardwareAccelerationDesc": { - "message": "Implicite tiu ĉi agordo estas ŝaltita. Malŝaltu nur se vi spertas grafikajn problemojn. Postulata estas relanĉo." + "message": "Tiu ĉi agordo estas implicite ŝaltita. Malŝaltu nur se vi spertas grafikajn problemojn. Necesas relanĉi." }, "approve": { "message": "Aprobi" @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generi pasvorton" + }, + "generatePassphrase": { + "message": "Generi pasfrazon" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Uzi ĉi tiun retpoŝton" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Hazarda" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 154bb327689..89447c73765 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Otro" }, - "generatePassword": { - "message": "Generar contraseña" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Tipo" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generar contraseña" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Usar este correo electrónico" }, + "useThisPassword": { + "message": "Usar esta contraseña" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Usar este nombre de usuario" + }, "random": { "message": "Aleatorio" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Contraseña débil encontrada en una filtración de datos. Utilice una contraseña única para proteger su cuenta. ¿Está seguro de que desea utilizar una contraseña comprometida?" }, - "useThisPassword": { - "message": "Usar esta contraseña" - }, - "useThisUsername": { - "message": "Usar este nombre de usuario" - }, "checkForBreaches": { "message": "Comprobar filtración de datos conocidos para esta contraseña" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 1cc526ecfba..f8605d9d4db 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Muu" }, - "generatePassword": { - "message": "Loo parool" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Tüüp" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Loo parool" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Juhuslik" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Tuvastati nõrk ning andmelekkes lekkinud ülemparool. Kasuta konto paremaks turvamiseks tugevamat parooli. Oled kindel, et soovid nõrga parooliga jätkata?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Otsi seda parooli teadaolevatest andmeleketest" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index ff8d91873b7..d2838dc22a1 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Bestelakoak" }, - "generatePassword": { - "message": "Sortu pasahitza" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Mota" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Sortu pasahitza" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Ausazkoa" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index e0050ea092a..3720eb1d6fa 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -351,12 +351,6 @@ "other": { "message": "ساير" }, - "generatePassword": { - "message": "تولید کلمه عبور" - }, - "generatePassphrase": { - "message": "تولید عبارت عبور" - }, "type": { "message": "نوع" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "تولید کننده نام کاربری" }, + "generatePassword": { + "message": "تولید کلمه عبور" + }, + "generatePassphrase": { + "message": "تولید عبارت عبور" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "مقدار باید بین $MIN$ و $MAX$ باشد.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "از این ایمیل استفاده شود" }, + "useThisPassword": { + "message": "از این کلمه عبور استفاده کن" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "از این نام کاربری استفاده کن" + }, "random": { "message": "تصادفی" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "کلمه عبور ضعیف شناسایی و در یک نقض داده پیدا شد. از یک کلمه عبور قوی و منحصر به فرد برای محافظت از حساب خود استفاده کنید. آیا مطمئنید که می‌خواهید از این کلمه عبور استفاده کنید؟" }, - "useThisPassword": { - "message": "از این کلمه عبور استفاده کن" - }, - "useThisUsername": { - "message": "از این نام کاربری استفاده کن" - }, "checkForBreaches": { "message": "نقض اطلاعات شناخته شده برای این کلمه عبور را بررسی کنید" }, @@ -3704,7 +3719,7 @@ "message": "تغییر کلمه عبور در معرض خطر" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "نمی‌توانید مجموعه‌هایی را که فقط دسترسی مشاهده دارند حذف کنید: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "برای تو در تو کردن یک پوشه، نام پوشه والد را وارد کرده و سپس یک “/” اضافه کنید. مثال: Social/Forums" }, + "sendsTitleNoItems": { + "message": "اطلاعات حساس را به‌صورت ایمن ارسال کنید", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "پرونده‌ها و داده‌های خود را به‌صورت امن با هر کسی، در هر پلتفرمی به اشتراک بگذارید. اطلاعات شما در حین اشتراک‌گذاری به‌طور کامل رمزگذاری انتها به انتها باقی خواهد ماند و میزان افشا محدود می‌شود.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "ساخت سریع کلمات عبور" + }, + "generatorNudgeBodyOne": { + "message": "به‌راحتی کلمات عبور قوی و منحصر به فرد ایجاد کنید با کلیک روی", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "برای کمک به حفظ امنیت ورودهای شما.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "با کلیک روی دکمه تولید رمز عبور، به‌راحتی کلمات عبور قوی و منحصر به‌ فرد ایجاد کنید تا ورودهای شما ایمن باقی بمانند.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "با پر کردن خودکار در وقت خود صرفه جویی کنید" }, diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index bffa90c8a17..3c34c7b3a38 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Muut" }, - "generatePassword": { - "message": "Luo salasana" - }, - "generatePassphrase": { - "message": "Luo salalause" - }, "type": { "message": "Tyyppi" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Käyttäjätunnusgeneraattori" }, + "generatePassword": { + "message": "Luo salasana" + }, + "generatePassphrase": { + "message": "Luo salalause" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Arvon tulee olla väliltä $MIN$—$MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Käytä tätä sähköpostia" }, + "useThisPassword": { + "message": "Käytä tätä salasanaa" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Käytä tätä käyttäjätunnusta" + }, "random": { "message": "Satunnainen" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Havaittiin heikko ja tietovuodosta löytynyt salasana. Sinun tulisi suojata tilisi vahvalla ja ainutlaatuisella salasanalla. Haluatko varmasti käyttää tätä salasanaa?" }, - "useThisPassword": { - "message": "Käytä tätä salasanaa" - }, - "useThisUsername": { - "message": "Käytä tätä käyttäjätunnusta" - }, "checkForBreaches": { "message": "Tarkasta esiintyykö salasanaa tunnetuissa tietovuodoissa" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Säästä aikaa automaattitäytöllä" }, diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 74c886a98b6..99209148ace 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Iba pa" }, - "generatePassword": { - "message": "Magtatag ng Password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Uri" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Magtatag ng Password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Mahinang password na nakilala at nakita sa data breach. Gamitin ang malakas at natatanging password upang makaproteksyon sa iyong account. Sigurado ka ba na gusto mong gamitin ang password na ito?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Tingnan ang kilalang breaches ng data para sa password na ito" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 2c53c0652bd..555286419c1 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Autre" }, - "generatePassword": { - "message": "Générer un mot de passe" - }, - "generatePassphrase": { - "message": "Générer une phrase de passe" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Générer un mot de passe" + }, + "generatePassphrase": { + "message": "Générer une phrase de passe" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "La valeur doit être comprise entre $MIN$ et $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Utiliser ce courriel" }, + "useThisPassword": { + "message": "Utiliser ce mot de passe" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Utiliser ce nom d'utilisateur" + }, "random": { "message": "Aléatoire" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Mot de passe faible identifié et trouvé dans une brèche de données. Utilisez un mot de passe robuste et unique pour protéger votre compte. Êtes-vous sûr de vouloir utiliser ce mot de passe ?" }, - "useThisPassword": { - "message": "Utiliser ce mot de passe" - }, - "useThisUsername": { - "message": "Utiliser ce nom d'utilisateur" - }, "checkForBreaches": { "message": "Vérifier les brèches de données connues pour ce mot de passe" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 17495f96785..d8173b1026a 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Other" }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 3999caa1bbd..ea117cb41a1 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -351,12 +351,6 @@ "other": { "message": "אחר" }, - "generatePassword": { - "message": "צור סיסמה" - }, - "generatePassphrase": { - "message": "צור ביטוי סיסמה" - }, "type": { "message": "סוג" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "צור סיסמה" + }, + "generatePassphrase": { + "message": "צור ביטוי סיסמה" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "הערך חייב להיות בין $MIN$ ל־$MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "השתמש בדוא\"ל זה" }, + "useThisPassword": { + "message": "השתמש בסיסמה זו" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "השתמש בשם משתמש זה" + }, "random": { "message": "אקראי" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "סיסמה חלשה זוהתה ונמצאה בפרצת נתונים. השתמש בסיסמה חזקה וייחודית כדי להגן על חשבונך. האם אתה בטוח שאתה רוצה להשתמש בסיסמה זו?" }, - "useThisPassword": { - "message": "השתמש בסיסמה זו" - }, - "useThisUsername": { - "message": "השתמש בשם משתמש זה" - }, "checkForBreaches": { "message": "בדוק פרצות נתונים ידועות עבור סיסמה זו" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 292e1c4b7d2..b6c6c7d2bcf 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Other" }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index cf90703a438..f03d52e0123 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Ostalo" }, - "generatePassword": { - "message": "Generiraj lozinku" - }, - "generatePassphrase": { - "message": "Generiraj fraznu lozinku" - }, "type": { "message": "Vrsta" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generiraj lozinku" + }, + "generatePassphrase": { + "message": "Generiraj fraznu lozinku" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Vrijednost mora biti u rasponu $MIN$ - $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Koristi ovu e-poštu" }, + "useThisPassword": { + "message": "Koristi ovu lozinku" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Koristi ovo korisničko ime" + }, "random": { "message": "Nasumično" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Slaba lozinka je nađena među ukradenima tijekom krađa podataka. Za zaštitu svog računa koristi jaku i jedinstvenu lozinku. Želiš li svejedno korisiti slabu, ukradenu lozinku?" }, - "useThisPassword": { - "message": "Koristi ovu lozinku" - }, - "useThisUsername": { - "message": "Koristi ovo korisničko ime" - }, "checkForBreaches": { "message": "Provjeri je li lozinka ukradena prilikom krađe podataka" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index f7eb77766c1..cfbc9856260 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Egyéb" }, - "generatePassword": { - "message": "Jelszó generálása" - }, - "generatePassphrase": { - "message": "Jelmondat generálás" - }, "type": { "message": "Típus" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Felhasználónév generátor" }, + "generatePassword": { + "message": "Jelszó generálása" + }, + "generatePassphrase": { + "message": "Jelmondat generálás" + }, + "passwordGenerated": { + "message": "A jelszó generálásra került." + }, + "passphraseGenerated": { + "message": "A jelmondat generálásra került." + }, + "usernameGenerated": { + "message": "A felhasználónév generálásra került." + }, + "emailGenerated": { + "message": "Az email generálásra került." + }, "spinboxBoundariesHint": { "message": "Az érték legyen $MIN$ és $MAX$ között.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Ezen email használata" }, + "useThisPassword": { + "message": "Jelszó használata" + }, + "useThisPassphrase": { + "message": "Jelmondat használata" + }, + "useThisUsername": { + "message": "Felhasználónév használata" + }, "random": { "message": "Véletlen" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Gyenge jelszó lett azonosítva és megtalálva egy adatvédelmi incidens során. A fók védelme érdekében használjunk erős és egyedi jelszót. Biztosan használni szeretnénk ezt a jelszót?" }, - "useThisPassword": { - "message": "Jelszó használata" - }, - "useThisUsername": { - "message": "Felhasználónév használata" - }, "checkForBreaches": { "message": "Az ehhez a jelszóhoz tartozó ismert adatvédelmi incidensek ellenőrzése" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Mappa beágyazása a szülőmappa nevének hozzáadásával, majd egy “/” karakterrel. Példa: Közösségi/Fórumok" }, + "sendsTitleNoItems": { + "message": "Érzékeny információt küldése biztonságosan", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Fájlok vagy adatok megosztása biztonságosan bárkivel, bármilyen platformon. Az információk titkosítva maradnak a végpontokon, korlátozva a kitettséget.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Jelszavak gyors létrehozása" + }, + "generatorNudgeBodyOne": { + "message": "Könnyen létrehozhatunk erős és egyedi jelszavakat a gombra kattintva", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "a bejelentkezések biztonságának megőrzéséhez.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Könnyedén hozhatunk létre erős és egyedi jelszavakat a Jelszó generálása gombra kattintva, amely segít megőrizni a bejelentkezések biztonságát.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Idő megtakarítás automatikus kitöltéssel" }, diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 70be61f4bf3..129e9fa87fa 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Lainnya" }, - "generatePassword": { - "message": "Buat Kata Sandi" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Tipe" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Buat Kata Sandi" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Acak" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Periksa pelanggaran data yang diketahui untuk kata sandi ini" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index a3dcb13fa76..bfd3163ded4 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Altro" }, - "generatePassword": { - "message": "Genera password" - }, - "generatePassphrase": { - "message": "Genera passphrase" - }, "type": { "message": "Tipo" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Genera password" + }, + "generatePassphrase": { + "message": "Genera passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Il valore deve essere compreso tra $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Usa questa e-mail" }, + "useThisPassword": { + "message": "Usa questa parola d'accesso" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Usa questo nome utente" + }, "random": { "message": "Casuale" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Password debole e trovata in una violazione dei dati. Usa una password forte e unica per proteggere il tuo account. Sei sicuro di voler usare questa password?" }, - "useThisPassword": { - "message": "Usa questa parola d'accesso" - }, - "useThisUsername": { - "message": "Usa questo nome utente" - }, "checkForBreaches": { "message": "Controlla se la tua password è presente in una violazione dei dati" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 624a296f32b..57ce174358d 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -351,12 +351,6 @@ "other": { "message": "その他" }, - "generatePassword": { - "message": "パスワードの自動生成" - }, - "generatePassphrase": { - "message": "パスフレーズを生成" - }, "type": { "message": "タイプ" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "パスワードの自動生成" + }, + "generatePassphrase": { + "message": "パスフレーズを生成" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "値は $MIN$ から $MAX$ の間でなければなりません。", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "このメールアドレスを使う" }, + "useThisPassword": { + "message": "このパスワードを使用する" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "このユーザー名を使用する" + }, "random": { "message": "ランダム" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "入力されたパスワードは脆弱かつすでに流出済みです。アカウントを守るためより強力で一意なパスワードを使用してください。本当にこの脆弱なパスワードを使用しますか?" }, - "useThisPassword": { - "message": "このパスワードを使用する" - }, - "useThisUsername": { - "message": "このユーザー名を使用する" - }, "checkForBreaches": { "message": "このパスワードの既知のデータ流出を確認" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 45047cd678f..4cdd0b39165 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -351,12 +351,6 @@ "other": { "message": "სხვა" }, - "generatePassword": { - "message": "პაროლის გენერირება" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "ტიპი" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "პაროლის გენერირება" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "შემთხვევითი" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 17495f96785..d8173b1026a 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Other" }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index e74fc479016..f15da49403c 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -351,12 +351,6 @@ "other": { "message": "ಇತರೆ" }, - "generatePassword": { - "message": "ಪಾಸ್ವರ್ಡ್ ರಚಿಸಿ" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "ಪ್ರಕಾರ" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "ಪಾಸ್ವರ್ಡ್ ರಚಿಸಿ" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 371f4cae1c8..81b57410d6b 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -351,12 +351,6 @@ "other": { "message": "기타" }, - "generatePassword": { - "message": "비밀번호 생성" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "유형" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "비밀번호 생성" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "무작위" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index f98a5dba2e1..c4a75ddb68d 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Kita" }, - "generatePassword": { - "message": "Sugeneruoti slaptažodį" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Tipas" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Sugeneruoti slaptažodį" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Atsitiktinis" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Silpnas slaptažodis nustatytas ir rastas per duomenų pažeidimą. Norėdami apsaugoti paskyrą, naudokite stiprų ir unikalų slaptažodį. Ar tikrai norite naudoti šį slaptažodį?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Patikrinti žinomus šio slaptažodžio duomenų pažeidimus" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 6c35520605d..c9e87e1aae9 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Cits" }, - "generatePassword": { - "message": "Veidot paroli" - }, - "generatePassphrase": { - "message": "Izveidot paroles vārdkopu" - }, "type": { "message": "Veids" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Lietotājvārdu veidotājs" }, + "generatePassword": { + "message": "Veidot paroli" + }, + "generatePassphrase": { + "message": "Izveidot paroles vārdkopu" + }, + "passwordGenerated": { + "message": "Parole izveidota" + }, + "passphraseGenerated": { + "message": "Paroles vārdkopa izveidota" + }, + "usernameGenerated": { + "message": "Lietotājvārds izveidots" + }, + "emailGenerated": { + "message": "E-pasta adrese izveidota" + }, "spinboxBoundariesHint": { "message": "Vērtībai jābūt starp $MIN$ un $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Izmantot šo e-pasta adresi" }, + "useThisPassword": { + "message": "Izmantot šo paroli" + }, + "useThisPassphrase": { + "message": "Izmantot šo paroles vārdkopu" + }, + "useThisUsername": { + "message": "Izmantot šo lietotājvārdu" + }, "random": { "message": "Nejauši" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Noteikta vāja parole, un tā ir atrasta datu noplūdē. Jāizmanto spēcīga un neatkārtojama parole, lai aizsargātu savu kontu. Vai tiešām izmantot šo paroli?" }, - "useThisPassword": { - "message": "Izmantot šo paroli" - }, - "useThisUsername": { - "message": "Izmantot šo lietotājvārdu" - }, "checkForBreaches": { "message": "Meklēt šo paroli zināmās datu noplūdēs" }, @@ -3704,7 +3719,7 @@ "message": "Mainīt riskam pakļautu paroli" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "Nevar noņemt krājumus ar tiesībām \"Tikai skatīt\": $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Apakšmapes var izveidot, ja pievieno iekļaujošās mapes nosaukumu, aiz kura ir \"/\". Piemēram: Tīklošanās/Forumi" }, + "sendsTitleNoItems": { + "message": "Drošā veidā nosūti jūtīgu informāciju", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Kopīgo datnes un datus drošā veidā ar ikvienu jebkurā platformā! Tava informācija paliks pilnībā šifrēta, vienlaikus ierobežojot riskantumu.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Ātra paroļu izveidošana" + }, + "generatorNudgeBodyOne": { + "message": "Vienkārša spēcīgu un neatkārtojamu paroļu izveidošana ar", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": ", lai palīdzētu uzturērt pieteikšanās vienumus drošus.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Vienkārša spēcīgu un neatkārtojamu paroļu izveidošana ar pogu \"Izveidot paroli\", lai palīdzētu uzturēt pieteikšanās vienumus drošus.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Laika ietaupīšana ar automātisko aizpildi" }, diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 51310719f0a..4a8e7af8d92 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Drugo" }, - "generatePassword": { - "message": "Generiši lozinku" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Tip" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generiši lozinku" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index a760acb86ec..00e0e52db49 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -351,12 +351,6 @@ "other": { "message": "മറ്റുള്ളവ" }, - "generatePassword": { - "message": "പാസ്‌വേഡ് സൃഷ്ടിക്കുക" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "തരം" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "പാസ്‌വേഡ് സൃഷ്ടിക്കുക" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 17495f96785..d8173b1026a 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Other" }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 2179311c501..10105784c4a 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Other" }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 3ddf3e44e5f..27b99440b01 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Annet" }, - "generatePassword": { - "message": "Generer et passord" - }, - "generatePassphrase": { - "message": "Generér passordfrase" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Brukernavngenerator" }, + "generatePassword": { + "message": "Generer et passord" + }, + "generatePassphrase": { + "message": "Generér passordfrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Verdien må være mellom $MIN$ og $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Bruk denne E-postadressen" }, + "useThisPassword": { + "message": "Bruk dette passordet" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Bruk dette brukernavnet" + }, "random": { "message": "Tilfeldig" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Bruk dette passordet" - }, - "useThisUsername": { - "message": "Bruk dette brukernavnet" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Spar tid med auto-utfylling" }, diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index 9ae7b7af955..608a01c34da 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Other" }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index b1e04600908..5843392d342 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Overig" }, - "generatePassword": { - "message": "Genereer wachtwoord" - }, - "generatePassphrase": { - "message": "Wachtwoordzin genereren" - }, "type": { "message": "Categorie" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Gebruikersnaamgenerator" }, + "generatePassword": { + "message": "Genereer wachtwoord" + }, + "generatePassphrase": { + "message": "Wachtwoordzin genereren" + }, + "passwordGenerated": { + "message": "Wachtwoord gegenereerd" + }, + "passphraseGenerated": { + "message": "Wachtwoordzin gegenereerd" + }, + "usernameGenerated": { + "message": "Gebruikersnaam gegenereerd" + }, + "emailGenerated": { + "message": "E-mail gegenereerd" + }, "spinboxBoundariesHint": { "message": "Waarde moet tussen $MIN$ en $MAX$ liggen.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Dit e-mailadres gebruiken" }, + "useThisPassword": { + "message": "Dit wachtwoord gebruiken" + }, + "useThisPassphrase": { + "message": "Deze wachtwoordzin gebruiken" + }, + "useThisUsername": { + "message": "Deze gebruikersnaam gebruiken" + }, "random": { "message": "Willekeurig" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Zwak wachtwoord geïdentificeerd en gevonden in een datalek. Gebruik een sterk en uniek wachtwoord om je account te beschermen. Weet je zeker dat je dit wachtwoord wilt gebruiken?" }, - "useThisPassword": { - "message": "Dit wachtwoord gebruiken" - }, - "useThisUsername": { - "message": "Deze gebruikersnaam gebruiken" - }, "checkForBreaches": { "message": "Bekende datalekken voor dit wachtwoord controleren" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Je kunt een map onderbrengen door het toevoegen van de naam van de bovenliggende map gevolgd door een \"/\". Voorbeeld: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Gevoelige informatie veilig versturen", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Deel bestanden en gegevens veilig met iedereen, op elk platform. Je informatie blijft end-to-end versleuteld terwijl en blootstelling beperkt.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Snel wachtwoorden maken" + }, + "generatorNudgeBodyOne": { + "message": "Maak eenvoudig sterke en unieke wachtwoorden door te klikken op", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "om je te helpen je inloggegevens veilig te houden.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Maak eenvoudig sterke en unieke wachtwoorden door op de knop Wachtwoord genereren te klikken om je logins veilig te houden.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Tijd besparen met automatisch aanvullen" }, diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 097da5886a6..632b556c53c 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Andre" }, - "generatePassword": { - "message": "Generer passord" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generer passord" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index a71275e64ac..e5fd6b10bb9 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -351,12 +351,6 @@ "other": { "message": "ଅନ୍ୟ" }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "ପ୍ରକାର" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 84ef8f0ca9a..769e63c4ef9 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Inne" }, - "generatePassword": { - "message": "Wygeneruj hasło" - }, - "generatePassphrase": { - "message": "Wygeneruj hasło wyrazowe" - }, "type": { "message": "Rodzaj" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Wygeneruj hasło" + }, + "generatePassphrase": { + "message": "Wygeneruj hasło wyrazowe" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Wartość musi być pomiędzy $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Użyj tego adresu e-mail" }, + "useThisPassword": { + "message": "Użyj tego hasła" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Użyj tej nazwy użytkownika" + }, "random": { "message": "Losowa" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Słabe hasło ujawnione w wyniku naruszenia ochrony danych. Użyj silnego i unikalnego hasła, aby chronić swoje konto. Czy na pewno chcesz użyć tego hasła?" }, - "useThisPassword": { - "message": "Użyj tego hasła" - }, - "useThisUsername": { - "message": "Użyj tej nazwy użytkownika" - }, "checkForBreaches": { "message": "Sprawdź znane naruszenia ochrony danych tego hasła" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 43fa1087988..73f73b06f0b 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Outros" }, - "generatePassword": { - "message": "Gerar Senha" - }, - "generatePassphrase": { - "message": "Gerar frase secreta" - }, "type": { "message": "Tipo" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Gerar Senha" + }, + "generatePassphrase": { + "message": "Gerar frase secreta" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Valor deve ser entre $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Usar este e-mail" }, + "useThisPassword": { + "message": "Use esta senha" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use este nome de usuário" + }, "random": { "message": "Aleatório" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Senha fraca identificada e encontrada em um vazamento de dados. Use uma senha forte e única para proteger a sua conta. Tem certeza de que deseja usar essa senha?" }, - "useThisPassword": { - "message": "Use esta senha" - }, - "useThisUsername": { - "message": "Use este nome de usuário" - }, "checkForBreaches": { "message": "Verificar vazamentos de dados conhecidos para esta senha" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index dc40c91960e..bd878110e17 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Outros" }, - "generatePassword": { - "message": "Gerar palavra-passe" - }, - "generatePassphrase": { - "message": "Gerar frase de acesso" - }, "type": { "message": "Tipo" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Gerador de nomes de utilizador" }, + "generatePassword": { + "message": "Gerar palavra-passe" + }, + "generatePassphrase": { + "message": "Gerar frase de acesso" + }, + "passwordGenerated": { + "message": "Palavra-passe gerada" + }, + "passphraseGenerated": { + "message": "Frase de acesso gerada" + }, + "usernameGenerated": { + "message": "Nome de utilizador gerado" + }, + "emailGenerated": { + "message": "E-mail gerado" + }, "spinboxBoundariesHint": { "message": "O valor deve estar entre $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Utilizar este e-mail" }, + "useThisPassword": { + "message": "Utilizar esta palavra-passe" + }, + "useThisPassphrase": { + "message": "Utilizar esta frase de acesso" + }, + "useThisUsername": { + "message": "Utilizar este nome de utilizador" + }, "random": { "message": "Aleatório" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Palavra-passe fraca identificada e encontrada numa violação de dados. Utilize uma palavra-passe forte e única para proteger a sua conta. Tem a certeza de que pretende utilizar esta palavra-passe?" }, - "useThisPassword": { - "message": "Utilizar esta palavra-passe" - }, - "useThisUsername": { - "message": "Utilizar este nome de utilizador" - }, "checkForBreaches": { "message": "Verificar violações de dados conhecidas para esta palavra-passe" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Crie uma subpasta adicionando o nome da pasta principal seguido de um \"/\". Exemplo: Redes Sociais/Fóruns" }, + "sendsTitleNoItems": { + "message": "Envie informações sensíveis com segurança", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Partilhe ficheiros e dados de forma segura com qualquer pessoa, em qualquer plataforma. As suas informações permanecerão encriptadas ponto a ponto, limitando a exposição.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Criar rapidamente palavras-passe" + }, + "generatorNudgeBodyOne": { + "message": "Crie facilmente palavras-passe fortes e únicas clicando em", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "para o ajudar a manter as suas credenciais seguras.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Crie facilmente palavras-passe fortes e únicas clicando no botão Gerar palavra-passe para o ajudar a manter as suas credenciais seguras.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Poupe tempo com o preenchimento automático" }, diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index f108d420829..b678e4cc3a2 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Altele" }, - "generatePassword": { - "message": "Generare parolă" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Tip" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generare parolă" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Aleatoriu" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 52dea4d205c..ed19ee79cf0 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Прочее" }, - "generatePassword": { - "message": "Сгенерировать пароль" - }, - "generatePassphrase": { - "message": "Создать парольную фразу" - }, "type": { "message": "Тип" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Генератор имени пользователя" }, + "generatePassword": { + "message": "Сгенерировать пароль" + }, + "generatePassphrase": { + "message": "Создать парольную фразу" + }, + "passwordGenerated": { + "message": "Пароль создан" + }, + "passphraseGenerated": { + "message": "Парольная фраза создана" + }, + "usernameGenerated": { + "message": "Имя пользователя создано" + }, + "emailGenerated": { + "message": "Email создан" + }, "spinboxBoundariesHint": { "message": "Значение должно быть между $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Использовать этот email" }, + "useThisPassword": { + "message": "Использовать этот пароль" + }, + "useThisPassphrase": { + "message": "Использовать эту парольную фразу" + }, + "useThisUsername": { + "message": "Использовать это имя пользователя" + }, "random": { "message": "Случайно" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Обнаружен слабый пароль, найденный в утечке данных. Используйте надежный и уникальный пароль для защиты вашего аккаунта. Вы уверены, что хотите использовать этот пароль?" }, - "useThisPassword": { - "message": "Использовать этот пароль" - }, - "useThisUsername": { - "message": "Использовать это имя пользователя" - }, "checkForBreaches": { "message": "Проверять известные случаи утечки данных для этого пароля" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Создайте вложенную папку, добавив название родительской папки и символ \"/\". Пример: Сообщества/Форумы" }, + "sendsTitleNoItems": { + "message": "Безопасная отправка конфиденциальной информации", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Безопасно обменивайтесь файлами и данными с кем угодно на любой платформе. Ваша информация надежно шифруется и доступ к ней ограничен.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Быстрое создание паролей" + }, + "generatorNudgeBodyOne": { + "message": "Легко создавайте надежные и уникальные пароли, нажатием на кнопку,", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "чтобы обеспечить безопасность ваших логинов.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Легко создавайте надежные и уникальные пароли, нажатием на кнопку 'Сгенерировать пароль', чтобы обеспечить безопасность ваших логинов.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Экономьте время с помощью автозаполнения" }, diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 03124c345da..b7e37bf4484 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Other" }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 3ee4a800a61..112839176f7 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Ostatné" }, - "generatePassword": { - "message": "Generovať heslo" - }, - "generatePassphrase": { - "message": "Generovať prístupovú frázu" - }, "type": { "message": "Typ" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Generátor používateľského mena" }, + "generatePassword": { + "message": "Generovať heslo" + }, + "generatePassphrase": { + "message": "Generovať prístupovú frázu" + }, + "passwordGenerated": { + "message": "Heslo vygenerované" + }, + "passphraseGenerated": { + "message": "Prístupová fráza vygenerovaná" + }, + "usernameGenerated": { + "message": "Používateľské meno vygenerované" + }, + "emailGenerated": { + "message": "E-mail vygenerovaný" + }, "spinboxBoundariesHint": { "message": "Hodnota musí byť medzi $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Použiť tento e-mail" }, + "useThisPassword": { + "message": "Použiť toto heslo" + }, + "useThisPassphrase": { + "message": "Použiť túto prístupovú frázu" + }, + "useThisUsername": { + "message": "Použiť toto používateľské meno" + }, "random": { "message": "Náhodné" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Nájdené slabé heslo v uniknuných údajoch. Na ochranu svojho účtu používajte silné a jedinečné heslo. Naozaj chcete používať toto heslo?" }, - "useThisPassword": { - "message": "Použiť toto heslo" - }, - "useThisUsername": { - "message": "Použiť toto používateľské meno" - }, "checkForBreaches": { "message": "Skontrolovať známe úniky údajov pre toto heslo" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Vnorte priečinok pridaním názvu nadradeného priečinka a znaku \"/\". Príklad: Sociálne siete/Fóra" }, + "sendsTitleNoItems": { + "message": "Send, citlivé informácie bezpečne", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Bezpečne zdieľajte súbory a údaje s kýmkoľvek a na akejkoľvek platforme. Vaše informácie zostanú end-to-end zašifrované a zároveň sa obmedzí ich odhalenie.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Rýchle vytváranie hesiel" + }, + "generatorNudgeBodyOne": { + "message": "Jednoducho vytvorte silné a jedinečné heslá kliknutím na", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "aby ste mohli ochrániť prihlasovacie údaje.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Jednoducho vytvorte silné a jedinečné heslá kliknutím na tlačidlo Generovať heslo, aby ste zabezpečili prihlasovacie údaje.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Ušetrite čas s automatickým vypĺňaním" }, diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index e1f7dcd578b..d7c2d0e90df 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Drugo" }, - "generatePassword": { - "message": "Generiraj geslo" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Tip" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generiraj geslo" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index d13c28ae4da..29294115a43 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Остало" }, - "generatePassword": { - "message": "Генерисање лозинке" - }, - "generatePassphrase": { - "message": "Генеришите приступну фразу" - }, "type": { "message": "Тип" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Генератор корисничког имена" }, + "generatePassword": { + "message": "Генерисање лозинке" + }, + "generatePassphrase": { + "message": "Генеришите приступну фразу" + }, + "passwordGenerated": { + "message": "Лозинка генерисана" + }, + "passphraseGenerated": { + "message": "Приступна фраза је генерисана" + }, + "usernameGenerated": { + "message": "Корисничко име генерисано" + }, + "emailGenerated": { + "message": "Имејл генерисан" + }, "spinboxBoundariesHint": { "message": "Вредност мора бити између $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Користи овај имејл" }, + "useThisPassword": { + "message": "Употреби ову лозинку" + }, + "useThisPassphrase": { + "message": "Употреби ову приступну фразу" + }, + "useThisUsername": { + "message": "Употреби ово корисничко име" + }, "random": { "message": "Случајно" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Идентификована је слаба лозинка и пронађена у упаду података. Користите јаку и јединствену лозинку да заштитите свој налог. Да ли сте сигурни да желите да користите ову лозинку?" }, - "useThisPassword": { - "message": "Употреби ову лозинку" - }, - "useThisUsername": { - "message": "Употреби ово корисничко име" - }, "checkForBreaches": { "message": "Проверите познате упада података за ову лозинку" }, @@ -3704,7 +3719,7 @@ "message": "Променити ризичну лозинку" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "Не можете уклонити колекције са дозволама само за приказ: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Угнездите фасциклу додавањем имена надређене фасцкле праћеног знаком „/“. Пример: Друштвени/Форуми" }, + "sendsTitleNoItems": { + "message": "Шаљите бзбедно осетљиве информације", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Делите датотеке и податке безбедно са било ким, на било којој платформи. Ваше информације ће остати шифроване од почетка-до-краја уз ограничење изложености.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Брзо креирајте лозинке" + }, + "generatorNudgeBodyOne": { + "message": "Лако креирајте снажне и јединствене лозинке кликом на", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "да вам помогне да задржите своје пријаве сигурно.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Лако креирајте снажне и јединствене лозинке кликом на дугме „Генерирате лозинку“ да вам помогне да чувате своје пријаве на сигурно.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Уштедите време са ауто-пуњењем" }, diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index e4fbc6f5773..f9f60575613 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Annat" }, - "generatePassword": { - "message": "Generera lösenord" - }, - "generatePassphrase": { - "message": "Generera lösenfras" - }, "type": { "message": "Typ" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generera lösenord" + }, + "generatePassphrase": { + "message": "Generera lösenfras" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Slumpmässig" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Lösenordet är svagt och avslöjades vid ett dataintrång. Använd ett starkt och unikt lösenord för att skydda ditt konto. Är det säkert att du vill använda detta lösenord?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Kontrollera kända dataintrång för detta lösenord" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 17495f96785..d8173b1026a 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Other" }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "Type" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index 34669ba21c4..c64ff409b19 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -351,12 +351,6 @@ "other": { "message": "อื่น ๆ" }, - "generatePassword": { - "message": "สร้างรหัสผ่าน" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "type": { "message": "ประเภท" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "สร้างรหัสผ่าน" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Random" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Check known data breaches for this password" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 43a134abfb0..65e5676258d 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Diğer" }, - "generatePassword": { - "message": "Parola oluştur" - }, - "generatePassphrase": { - "message": "Parola üret" - }, "type": { "message": "Tür" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Kullanıcı adı üreteci" }, + "generatePassword": { + "message": "Parola oluştur" + }, + "generatePassphrase": { + "message": "Parola üret" + }, + "passwordGenerated": { + "message": "Parola üretildi" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Kullanıcı adı üretildi" + }, + "emailGenerated": { + "message": "E-posta üretildi" + }, "spinboxBoundariesHint": { "message": "Değer $MIN$ ile $MAX$ arasında olmalıdır.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Bu e-postayı kullan" }, + "useThisPassword": { + "message": "Bu parolayı kullan" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Bu kullanıcı adını kullan" + }, "random": { "message": "Rasgele" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Hem zayıf hem de veri ihlalinde yer alan bir tespit edildi. Hesabınızı korumak için güçlü bir parola seçin ve o parolayı başka yerlerde kullanmayın. Bu parolayı kullanmak istediğinizden emin misiniz?" }, - "useThisPassword": { - "message": "Bu parolayı kullan" - }, - "useThisUsername": { - "message": "Bu kullanıcı adını kullan" - }, "checkForBreaches": { "message": "Bilinen veri ihlallerinde bu parolayı kontrol et" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Üst klasörün adının sonuna “/” ekleyerek klasörleri iç içe koyabilirsiniz. Örnek: Sosyal/Forumlar" }, + "sendsTitleNoItems": { + "message": "Hassas bilgileri güvenle paylaşın", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Dosyaları ve verileri istediğiniz kişilerle, istediğiniz platformda paylaşın. Bilgileriniz başkalarının eline geçmemesi için uçtan şifrelenecektir.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Hızlıca parola oluşturun" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Otomatik doldurmayla zaman kazanın" }, diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index a4a63da8089..f6fa1d7a6ee 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Інше" }, - "generatePassword": { - "message": "Генерувати пароль" - }, - "generatePassphrase": { - "message": "Генерувати парольну фразу" - }, "type": { "message": "Тип" }, @@ -2513,13 +2507,13 @@ "message": "Головний пароль вилучено" }, "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + "message": "Головний пароль більше не є обов'язковим для учасників зазначеної організації. Підтвердьте вказаний нижче домен з адміністратором вашої організації." }, "organizationName": { - "message": "Organization name" + "message": "Назва організації" }, "keyConnectorDomain": { - "message": "Key Connector domain" + "message": "Домен Key Connector" }, "leaveOrganization": { "message": "Покинути організацію" @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Генератор імені користувача" }, + "generatePassword": { + "message": "Генерувати пароль" + }, + "generatePassphrase": { + "message": "Генерувати парольну фразу" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Значення має бути між $MIN$ та $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Використати цю е-пошту" }, + "useThisPassword": { + "message": "Використати цей пароль" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Використати це ім'я користувача" + }, "random": { "message": "Випадково" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Виявлено слабкий пароль, який знайдено у витоку даних. Використовуйте надійний та унікальний пароль для захисту свого облікового запису. Ви дійсно хочете використати цей пароль?" }, - "useThisPassword": { - "message": "Використати цей пароль" - }, - "useThisUsername": { - "message": "Використати це ім'я користувача" - }, "checkForBreaches": { "message": "Перевірити відомі витоки даних для цього пароля" }, @@ -3704,7 +3719,7 @@ "message": "Змінити ризикований пароль" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "Ви не можете вилучати збірки, маючи дозвіл лише на перегляд: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -3716,13 +3731,38 @@ "message": "Перемістити" }, "newFolder": { - "message": "New folder" + "message": "Нова тека" }, "folderName": { - "message": "Folder Name" + "message": "Назва теки" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Зробіть теку вкладеною, вказавши після основної теки \"/\". Наприклад: Обговорення/Форуми" + }, + "sendsTitleNoItems": { + "message": "Безпечно надсилайте конфіденційну інформацію", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Безпечно діліться файлами й даними з ким завгодно, на будь-якій платформі. Ваша інформація наскрізно зашифрована та має обмежений доступ.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Швидко створюйте паролі" + }, + "generatorNudgeBodyOne": { + "message": "Легко створюйте надійні та унікальні паролі, натиснувши на", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "щоб зберегти свої записи в безпеці.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Легко створюйте надійні та унікальні паролі, натиснувши кнопку Генерувати пароль, щоб зберегти свої записи в безпеці.", + "description": "Aria label for the body content of the generator nudge" }, "newLoginNudgeTitle": { "message": "Заощаджуйте час з автозаповненням" diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 4ab9257691c..a880f7338d7 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -351,12 +351,6 @@ "other": { "message": "Khác" }, - "generatePassword": { - "message": "Tạo mật khẩu" - }, - "generatePassphrase": { - "message": "Tạo cụm mật khẩu" - }, "type": { "message": "Loại" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "Tạo mật khẩu" + }, + "generatePassphrase": { + "message": "Tạo cụm mật khẩu" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "Use this password" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "Use this username" + }, "random": { "message": "Ngẫu nhiên" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "Mật khẩu yếu này đã bị rò rỉ trong một vụ tấn công dữ liệu. Dùng mật khẩu mới và an toàn để bảo vệ tài khoản bạn. Bạn có chắc muốn sử dụng mật khẩu đã bị rò rỉ?" }, - "useThisPassword": { - "message": "Use this password" - }, - "useThisUsername": { - "message": "Use this username" - }, "checkForBreaches": { "message": "Kiểm tra mật khẩu có lộ trong các vụ rò rỉ dữ liệu hay không" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 69d5faa5fb7..4a001fc3b05 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -351,12 +351,6 @@ "other": { "message": "其他" }, - "generatePassword": { - "message": "生成密码" - }, - "generatePassphrase": { - "message": "生成密码短语" - }, "type": { "message": "类型" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "用户名生成器" }, + "generatePassword": { + "message": "生成密码" + }, + "generatePassphrase": { + "message": "生成密码短语" + }, + "passwordGenerated": { + "message": "密码已生成" + }, + "passphraseGenerated": { + "message": "密码短语已生成" + }, + "usernameGenerated": { + "message": "用户名已生成" + }, + "emailGenerated": { + "message": "电子邮箱已生成" + }, "spinboxBoundariesHint": { "message": "值必须在 $MIN$ 和 $MAX$ 之间。", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "使用此电子邮箱" }, + "useThisPassword": { + "message": "使用此密码" + }, + "useThisPassphrase": { + "message": "使用此密码短语" + }, + "useThisUsername": { + "message": "使用此用户名" + }, "random": { "message": "随机" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "识别到弱密码且其出现在数据泄露中。请使用一个强且唯一的密码以保护您的账户。确定要使用这个密码吗?" }, - "useThisPassword": { - "message": "使用此密码" - }, - "useThisUsername": { - "message": "使用此用户名" - }, "checkForBreaches": { "message": "检查已知的数据泄露是否包含此密码" }, @@ -3704,7 +3719,7 @@ "message": "更改有风险的密码" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "您无法移除仅具有「查看」权限的集合:$COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -3724,21 +3739,46 @@ "folderHintText": { "message": "通过在父文件夹名后面添加「/」来嵌套文件夹。示例:Social/Forums" }, + "sendsTitleNoItems": { + "message": "安全地发送敏感信息", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "在任何平台上安全地与任何人共享文件和数据。您的信息将在限制曝光的同时保持端到端加密。", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "快速创建密码" + }, + "generatorNudgeBodyOne": { + "message": "一键创建强大且唯一的密码", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "帮助您保持登录安全。", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "点击「生成密码」按钮,轻松创建强大且唯一的密码,帮助您保持登录安全。", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "使用自动填充节省时间" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "包含", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "网站", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "以便将此登录显示为自动填充建议。", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 6080c3ee0f8..f39eee97118 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -351,12 +351,6 @@ "other": { "message": "其他" }, - "generatePassword": { - "message": "產生密碼" - }, - "generatePassphrase": { - "message": "產生密碼片語" - }, "type": { "message": "類型" }, @@ -2633,6 +2627,24 @@ "usernameGenerator": { "message": "Username generator" }, + "generatePassword": { + "message": "產生密碼" + }, + "generatePassphrase": { + "message": "產生密碼片語" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2686,6 +2698,15 @@ "useThisEmail": { "message": "Use this email" }, + "useThisPassword": { + "message": "使用此密碼" + }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, + "useThisUsername": { + "message": "使用此使用者名稱" + }, "random": { "message": "隨機" }, @@ -3051,12 +3072,6 @@ "weakAndBreachedMasterPasswordDesc": { "message": "密碼強度不足,且該密碼已洩露。請使用一個強度足夠和獨特的密碼來保護您的帳戶。您確定要使用這個密碼嗎?" }, - "useThisPassword": { - "message": "使用此密碼" - }, - "useThisUsername": { - "message": "使用此使用者名稱" - }, "checkForBreaches": { "message": "檢查外洩的密碼資料庫中是否包含此密碼" }, @@ -3724,6 +3739,31 @@ "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, From cdee9169721a206cc73d374138d845aa930e75f9 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 09:14:55 +0000 Subject: [PATCH 189/245] Autosync the updated translations (#14998) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 52 +- apps/web/src/locales/ar/messages.json | 52 +- apps/web/src/locales/az/messages.json | 52 +- apps/web/src/locales/be/messages.json | 52 +- apps/web/src/locales/bg/messages.json | 52 +- apps/web/src/locales/bn/messages.json | 52 +- apps/web/src/locales/bs/messages.json | 52 +- apps/web/src/locales/ca/messages.json | 52 +- apps/web/src/locales/cs/messages.json | 52 +- apps/web/src/locales/cy/messages.json | 52 +- apps/web/src/locales/da/messages.json | 52 +- apps/web/src/locales/de/messages.json | 52 +- apps/web/src/locales/el/messages.json | 52 +- apps/web/src/locales/en_GB/messages.json | 52 +- apps/web/src/locales/en_IN/messages.json | 52 +- apps/web/src/locales/eo/messages.json | 56 +- apps/web/src/locales/es/messages.json | 52 +- apps/web/src/locales/et/messages.json | 196 ++- apps/web/src/locales/eu/messages.json | 52 +- apps/web/src/locales/fa/messages.json | 1784 +++++++++++----------- apps/web/src/locales/fi/messages.json | 52 +- apps/web/src/locales/fil/messages.json | 52 +- apps/web/src/locales/fr/messages.json | 52 +- apps/web/src/locales/gl/messages.json | 52 +- apps/web/src/locales/he/messages.json | 52 +- apps/web/src/locales/hi/messages.json | 52 +- apps/web/src/locales/hr/messages.json | 52 +- apps/web/src/locales/hu/messages.json | 52 +- apps/web/src/locales/id/messages.json | 52 +- apps/web/src/locales/it/messages.json | 52 +- apps/web/src/locales/ja/messages.json | 52 +- apps/web/src/locales/ka/messages.json | 52 +- apps/web/src/locales/km/messages.json | 52 +- apps/web/src/locales/kn/messages.json | 52 +- apps/web/src/locales/ko/messages.json | 52 +- apps/web/src/locales/lv/messages.json | 52 +- apps/web/src/locales/ml/messages.json | 52 +- apps/web/src/locales/mr/messages.json | 154 +- apps/web/src/locales/my/messages.json | 52 +- apps/web/src/locales/nb/messages.json | 52 +- apps/web/src/locales/ne/messages.json | 52 +- apps/web/src/locales/nl/messages.json | 52 +- apps/web/src/locales/nn/messages.json | 52 +- apps/web/src/locales/or/messages.json | 52 +- apps/web/src/locales/pl/messages.json | 52 +- apps/web/src/locales/pt_BR/messages.json | 52 +- apps/web/src/locales/pt_PT/messages.json | 52 +- apps/web/src/locales/ro/messages.json | 52 +- apps/web/src/locales/ru/messages.json | 52 +- apps/web/src/locales/si/messages.json | 52 +- apps/web/src/locales/sk/messages.json | 52 +- apps/web/src/locales/sl/messages.json | 52 +- apps/web/src/locales/sr/messages.json | 52 +- apps/web/src/locales/sr_CS/messages.json | 52 +- apps/web/src/locales/sv/messages.json | 52 +- apps/web/src/locales/te/messages.json | 52 +- apps/web/src/locales/th/messages.json | 52 +- apps/web/src/locales/tr/messages.json | 52 +- apps/web/src/locales/uk/messages.json | 68 +- apps/web/src/locales/vi/messages.json | 52 +- apps/web/src/locales/zh_CN/messages.json | 58 +- apps/web/src/locales/zh_TW/messages.json | 52 +- 62 files changed, 3854 insertions(+), 1374 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 3d2c04f6673..d1fac1d1357 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -547,12 +547,6 @@ "message": "Tokkel invou", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Genereer Wagwoord" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Gaan na of wagwoord blootgestel is." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Genereer Wagwoord" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 82907ed4df0..e27a87efa31 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -547,12 +547,6 @@ "message": "تبديل الطي", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "توليد كلمة مرور" - }, - "generatePassphrase": { - "message": "توليد عبارة مرور" - }, "checkPassword": { "message": "تحقق مما إذا تم الكشف عن كلمة المرور." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "توليد كلمة مرور" + }, + "generatePassphrase": { + "message": "توليد عبارة مرور" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 121f50f0e00..2faf2cb7e12 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -547,12 +547,6 @@ "message": "Yığcamlaşdırmanı aç/bağla", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Parol yarat" - }, - "generatePassphrase": { - "message": "Keçid ifadələri yarat" - }, "checkPassword": { "message": "Parolun ifşalanıb ifşalanmadığını yoxlayın." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "E-poçt yarat" }, + "generatePassword": { + "message": "Parol yarat" + }, + "generatePassphrase": { + "message": "Keçid ifadələri yarat" + }, + "passwordGenerated": { + "message": "Parol yaradıldı" + }, + "passphraseGenerated": { + "message": "Keçid ifadəsi yaradıldı" + }, + "usernameGenerated": { + "message": "İstifadəçi adı yaradıldı" + }, + "emailGenerated": { + "message": "E-poçt yaradıldı" + }, "spinboxBoundariesHint": { "message": "Dəyər, $MIN$-$MAX$ arasında olmalıdır.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Bu parolu istifadə et" }, + "useThisPassphrase": { + "message": "Bu keçid ifadəsini istifadə et" + }, "useThisUsername": { "message": "Bu istifadəçi adını istifadə et" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "Yeni biznes vahidi" }, + "sendsTitleNoItems": { + "message": "Send, həssas məlumatlar təhlükəsizdir", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "İstənilən platformada faylları və dataları hər kəslə paylaşın. İfşa olunmağı məhdudlaşdıraraq məlumatlarınız ucdan-uca şifrələnmiş qalacaq.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Cəld parol yaradın" + }, + "generatorNudgeBodyOne": { + "message": "Klikləyərək güclü və unikal parolları asanlıqla yaradın", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "və girişlərinizi güvənli şəkildə saxlayın.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Girişlərinizi güvənli şəkildə saxlamağınıza kömək etməsi üçün Parol yarat düyməsinə klikləyərək güclü və unikal parolları asanlıqla yaradın.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Avto-doldurma ilə vaxta qənaət edin" }, diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 7409df6baff..c62928d4bdc 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -547,12 +547,6 @@ "message": "Згарнуць/Разгарнуць", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Генерыраваць пароль" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Праверце, ці не скампраметаваны пароль." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Генерыраваць пароль" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index a8bd9d51e8f..436388652f4 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -547,12 +547,6 @@ "message": "Превключване на свиването", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Нова парола" - }, - "generatePassphrase": { - "message": "Генериране на парола-фраза" - }, "checkPassword": { "message": "Проверка дали паролата е разкрита." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Генериране на електронна поща" }, + "generatePassword": { + "message": "Нова парола" + }, + "generatePassphrase": { + "message": "Генериране на парола-фраза" + }, + "passwordGenerated": { + "message": "Паролата е генерирана" + }, + "passphraseGenerated": { + "message": "Паролата-фраза е генерирана" + }, + "usernameGenerated": { + "message": "Потребителското име е генерирано" + }, + "emailGenerated": { + "message": "Е-пощата е генерирана" + }, "spinboxBoundariesHint": { "message": "Стойността трябва да бъде между $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Използване на тази парола" }, + "useThisPassphrase": { + "message": "Използване на тази парола-фраза" + }, "useThisUsername": { "message": "Използване на това потребителско име" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "Нова бизнес единица" }, + "sendsTitleNoItems": { + "message": "Изпращайте чувствителна информация сигурно", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Споделяйте сигурно файлове и данни с всекиго, през всяка система. Информацията Ви ще бъде защитена с шифроване от край до край, а видимостта ѝ ще бъде ограничена.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Създавайте пароли бързо" + }, + "generatorNudgeBodyOne": { + "message": "Създавайте лесно сложни и уникални пароли като щракнете върху", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "за да защитите данните си за вписване.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Създавайте лесно сложни и уникални пароли като щракнете върху бутона за генериране на парола, за да защитите данните си за вписване.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Спестете време с автоматично попълване" }, diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 1419d580837..1a8352a6dbf 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "পাসওয়ার্ড তৈরি করুন" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "পাসওয়ার্ড তৈরি করুন" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 31174ebbe5a..9295e7e5610 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -547,12 +547,6 @@ "message": "Sažmi/Proširi", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 49aadd7951a..d5001893490 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -547,12 +547,6 @@ "message": "Redueix/Amplia", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Genera contrasenya" - }, - "generatePassphrase": { - "message": "Genera frase de pas" - }, "checkPassword": { "message": "Comprova si la contrasenya ha estat exposada." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Genera contrasenya" + }, + "generatePassphrase": { + "message": "Genera frase de pas" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "Unitat de negoci nova" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 615bfa4f09b..9db811ff776 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -547,12 +547,6 @@ "message": "Přepnout sbalení", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Vygenerovat heslo" - }, - "generatePassphrase": { - "message": "Vygenerovat heslovou frázi" - }, "checkPassword": { "message": "Zkontrolujte, zda nedošlo k úniku hesla." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Vygenerovat e-mail" }, + "generatePassword": { + "message": "Vygenerovat heslo" + }, + "generatePassphrase": { + "message": "Vygenerovat heslovou frázi" + }, + "passwordGenerated": { + "message": "Heslo bylo vygenerováno" + }, + "passphraseGenerated": { + "message": "Heslová fráze byla vygenerována" + }, + "usernameGenerated": { + "message": "Uživatelské jméno bylo vygenerováno" + }, + "emailGenerated": { + "message": "E-mail byl vygenerován" + }, "spinboxBoundariesHint": { "message": "Hodnota musí být mezi $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Použít toto heslo" }, + "useThisPassphrase": { + "message": "Použít tuto heslovou frázi" + }, "useThisUsername": { "message": "Použít toto uživatelské jméno" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "Nová obchodní jednotka" }, + "sendsTitleNoItems": { + "message": "Posílejte citlivé informace bezpečně", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Sdílejte bezpečně soubory a data s kýmkoli na libovolné platformě. Vaše informace zůstanou šifrovány a zároveň omezují expozici.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Rychlé vytvoření hesla" + }, + "generatorNudgeBodyOne": { + "message": "Jednoduše vytvořte silná a unikátní hesla klepnutím na", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "aby Vám pomohlo udržet Vaše přihlašovací údaje v bezpečí.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Jednoduše vytvořte silná a unikátní hesla klepnutím na tlačítko Generovat heslo, které Vám pomůže udržet Vaše přihlašovací údaje v bezpečí.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Ušetřete čas s automatickým vyplňováním" }, diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 87c852a8869..78d1c04a3c9 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index ee2dc4d570c..db73ea4784c 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -547,12 +547,6 @@ "message": "Fold sammen/ud", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generér adgangskode" - }, - "generatePassphrase": { - "message": "Generér adgangssætning" - }, "checkPassword": { "message": "Tjek, om adgangskode er blevet kompromitteret." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generér e-mail" }, + "generatePassword": { + "message": "Generér adgangskode" + }, + "generatePassphrase": { + "message": "Generér adgangssætning" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Værdi skal være mellem $MIN$ og $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Anvend denne adgangskode" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Anvend dette brugernavn" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 58b4c568c61..bcf308bedd5 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -547,12 +547,6 @@ "message": "Ein-/ausklappen", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Passwort generieren" - }, - "generatePassphrase": { - "message": "Passphrase generieren" - }, "checkPassword": { "message": "Überprüfen ob ihr Kennwort kompromittiert ist." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "E-Mail generieren" }, + "generatePassword": { + "message": "Passwort generieren" + }, + "generatePassphrase": { + "message": "Passphrase generieren" + }, + "passwordGenerated": { + "message": "Passwort generiert" + }, + "passphraseGenerated": { + "message": "Passphrase generiert" + }, + "usernameGenerated": { + "message": "Benutzername generiert" + }, + "emailGenerated": { + "message": "E-Mail-Adresse generiert" + }, "spinboxBoundariesHint": { "message": "Wert muss zwischen $MIN$ und $MAX$ liegen.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Dieses Passwort verwenden" }, + "useThisPassphrase": { + "message": "Diese Passphrase verwenden" + }, "useThisUsername": { "message": "Diesen Benutzernamen verwenden" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "Neuer Geschäftsbereich" }, + "sendsTitleNoItems": { + "message": "Sensible Informationen sicher versenden", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Teile Dateien und Daten sicher mit jedem auf jeder Plattform. Deine Informationen bleiben Ende-zu-Ende-verschlüsselt, während die Verbreitung begrenzt wird.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Passwörter schnell erstellen" + }, + "generatorNudgeBodyOne": { + "message": "Generiere ganz einfach starke und einzigartige Passwörter, indem du auf den", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": ", um dir zu helfen, deine Zugangsdaten sicher zu halten.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Generiere ganz einfach starke und einzigartige Passwörter, indem du auf den \"Passwort generieren\"-Button klickst, um dir zu helfen, deine Zugangsdaten sicher zu halten.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Spare Zeit mit Auto-Ausfüllen" }, diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 3790920f883..60aa8db716c 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -547,12 +547,6 @@ "message": "Εναλλαγή Σύμπτυξης", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Δημιουργία Κωδικού" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Ελέγξτε εάν ο κωδικός έχει εκτεθεί." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Δημιουργία Κωδικού" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Χρήση αυτού του κωδικού πρόσβασης" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Χρήση αυτού του ονόματος χρήστη" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index ab7bf3a1197..7dd11351f89 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 736cece1de3..5b8f0f521fb 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 0a4acdc5739..14a15e2965c 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -534,7 +534,7 @@ "description": "URI match detection for auto-fill." }, "defaultMatchDetection": { - "message": "Implicita eltrovo en akordo", + "message": "Implicita eltrovado en akordo", "description": "Default URI match detection for auto-fill." }, "never": { @@ -547,12 +547,6 @@ "message": "Baskuli Fali", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generi pasvorton" - }, - "generatePassphrase": { - "message": "Generi pasfrazon" - }, "checkPassword": { "message": "Kontrolu ĉu pasvorto estis elmontrita." }, @@ -1873,7 +1867,7 @@ "message": "Ĉu vi zorgas pri tio, ke via konto estas ensalutinta sur alia aparato? Sekvu sube por senrajtigi ĉiujn komputilojn aŭ aparatojn, kiujn vi antaŭe uzis. Ĉi tiu sekureca paŝo rekomendas se vi antaŭe uzis publikan komputilon aŭ hazarde konservis vian pasvorton sur aparato, kiu ne estas via. Ĉi tiu paŝo ankaŭ malplenigos ĉiujn antaŭe memoritajn du-paŝajn ensalutajn sesiojn. " }, "deauthorizeSessionsWarning": { - "message": "Se vi daŭrigos vian adiaŭadon de la nuna seanco, necesos vin saluti denove. Oni ankaŭ demandos de vi du-faktoran aŭtentigon, se tiu elekteblo estas ebligita. La seancoj aktivaj sur aliaj aparatoj povas resti daŭre aktivaj ankoraŭ unu horon." + "message": "La daŭrigo ankaŭ elsalutos vin de via nuna sesio, postulante vin denove ensaluti. Oni ankaŭ petos vin du-ŝtupa ensaluto, se ĝi estas ebligita. Aktivaj sesioj sur aliaj aparatoj povas daŭre resti aktivaj ĝis ĝis unu horo. " }, "newDeviceLoginProtection": { "message": "Saluto per nova aparato" @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generi pasvorton" + }, + "generatePassphrase": { + "message": "Generi pasfrazon" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 1660429f7f4..b8a11b5e543 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -547,12 +547,6 @@ "message": "Colapsar/Expandir", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generar contraseña" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Comprobar si la contraseña está comprometida." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generar contraseña" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 19608e1fd82..85afcc55be1 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -420,7 +420,7 @@ "message": "Aegumise aasta" }, "authenticatorKeyTotp": { - "message": "Autentiseerimise võti (TOTP)" + "message": "Autentimise võti (TOTP)" }, "totpHelperTitle": { "message": "Muuda 2-astmeline kinnitamine sujuvaks" @@ -547,12 +547,6 @@ "message": "Näita vähem", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Loo parool" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Kontrolli, kas parool on lekkinud." }, @@ -838,13 +832,13 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Kopeeri veebilehe aadress" }, "copyNotes": { - "message": "Copy notes" + "message": "Kopeeri märkused" }, "copyAddress": { - "message": "Copy address" + "message": "Kopeeri aadress" }, "copyPhone": { "message": "Kopeeri telefoninumber" @@ -1058,7 +1052,7 @@ "message": "Bitwardeni rakenduse seadistuses peab olema konfigureeritud sisselogimine läbi seadme. Soovid teist valikut?" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "Soovid teist valikut kasutada?" }, "loginWithMasterPassword": { "message": "Logi sisse ülemparooliga" @@ -1193,10 +1187,10 @@ "message": "Autentimiseks vajuta nuppu oma YubiKey'l" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "Sisselogimise ajalõpp" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "Sisselogimise sessioon on aegunud. Palun alusta uuesti sisse logimist." }, "verifyYourIdentity": { "message": "Kinnitage oma Identiteet" @@ -1376,7 +1370,7 @@ "message": "Sul ei ole õigust vaadata kõiki asju selles kogus." }, "youDoNotHavePermissions": { - "message": "You do not have permissions to this collection" + "message": "Sul ei ole õigusi sellele kogumikule" }, "noCollectionsInList": { "message": "Puuduvad kollektsioonid, mida kuvada." @@ -1403,13 +1397,13 @@ "message": "Sinu seadmesse saadeti teavitus." }, "notificationSentDevicePart1": { - "message": "Unlock Bitwarden on your device or on the " + "message": "Ava Bitwarden oma seadmes või avades " }, "areYouTryingToAccessYourAccount": { "message": "Kas sa püüad praegu oma kontole sisse logida?" }, "accessAttemptBy": { - "message": "Access attempt by $EMAIL$", + "message": "$EMAIL$ proovis juurdepääsu saada", "placeholders": { "email": { "content": "$1", @@ -1427,10 +1421,10 @@ "message": "veebirakendus" }, "notificationSentDevicePart2": { - "message": "Make sure the Fingerprint phrase matches the one below before approving." + "message": "Enne kinnitamist kontrolli, et unikaalne sõnajada ühtib allolevaga." }, "notificationSentDeviceComplete": { - "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + "message": "Ava Bitwarden oma seadmes. Enne kinnitamist kontrolli, et unikaalne sõnajada vastab allolevaga." }, "aNotificationWasSentToYourDevice": { "message": "Sinu seadmele saadeti teavitus" @@ -1736,25 +1730,25 @@ "message": "Paroolide ajalugu" }, "generatorHistory": { - "message": "Generator history" + "message": "Genereerija ajalugu" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Tühjenda genereerija ajalugu" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Jätkates kustutatakse kõik kirjed genereerija ajaloost. Kas sa oled kindel, et soovid jätkata?" }, "noPasswordsInList": { "message": "Puuduvad paroolid, mida kuvada." }, "clearHistory": { - "message": "Clear history" + "message": "Tühjenda ajalugu" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Siin ei ole midagi näidata" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Sa ei ole midagi viimat genereerinud" }, "clear": { "message": "Tühjenda", @@ -1794,10 +1788,10 @@ "message": "Palun logi uuesti sisse." }, "currentSession": { - "message": "Current session" + "message": "Praegune sessioon" }, "requestPending": { - "message": "Request pending" + "message": "Taotlus on ootel" }, "logBackInOthersToo": { "message": "Palun logi uuesti sisse. Kui kasutad teisi Bitwardeni rakendusi, pead ka nendes uuesti sisse logima." @@ -1876,31 +1870,31 @@ "message": "Jätkatest logitakse sind ka käimasolevast sessioonist välja, mistõttu pead kontosse uuesti sisse logima. Lisaks võidakse küsida kaheastmelist kinnitust, kui see on sisse lülitatud. Teised kontoga ühendatud seadmed võivad jääda sisselogituks kuni üheks tunniks." }, "newDeviceLoginProtection": { - "message": "New device login" + "message": "Uuest seadmest logiti sisse" }, "turnOffNewDeviceLoginProtection": { - "message": "Turn off new device login protection" + "message": "Lülita välja uuest seadmest sisse logimise kaitse" }, "turnOnNewDeviceLoginProtection": { - "message": "Turn on new device login protection" + "message": "Lülita sisse uuest seadmest sisse logimise kaitse" }, "turnOffNewDeviceLoginProtectionModalDesc": { - "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + "message": "Jätka allpool, et lülitada välja kinnituskirjad, mis Bitwarden saadab iga kord, kui sa logid uue seadme kaudu." }, "turnOnNewDeviceLoginProtectionModalDesc": { - "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + "message": "Jätka allpool, et lülitada sisse kinnituskirjad, mis Bitwarden saadab iga kord, kui sa logid sisse uuest seadmest." }, "turnOffNewDeviceLoginProtectionWarning": { - "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + "message": "Ilma uue seadme logimise kaitseta saab igaüks igast seadmest sinu ülemparooliga sisse logida. Oma konto kaitsmiseks ilma kinnituskirjadeta, sea sisse kahe-astmeline kinnitamine." }, "accountNewDeviceLoginProtectionSaved": { - "message": "New device login protection changes saved" + "message": "Uue seadme sisselogimise kaitse muudatused salvestatud" }, "sessionsDeauthorized": { "message": "Kõikidest seadmetest on välja logitud" }, "accountIsOwnedMessage": { - "message": "This account is owned by $ORGANIZATIONNAME$", + "message": "Selle konto omanik on $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -2160,7 +2154,7 @@ "message": "Kaheastmelise kinnitamine aktiveerimine võib luua olukorra, kus sul on võimatu oma Bitwardeni kontosse sisse logida. Näiteks kui kaotad oma nutiseadme. Taastamise kood võimaldab aga kontole ligi pääseda ka olukorras, kus kaheastmelist kinnitamist ei ole võimalik läbi viia. Sellistel juhtudel ei saa ka Bitwardeni klienditugi sinu kontole ligipääsu taastada. Selle tõttu soovitame taastekoodi välja printida ja seda turvalises kohas hoida." }, "yourSingleUseRecoveryCode": { - "message": "Your single-use recovery code can be used to turn off two-step login in the event that you lose access to your two-step login provider. Bitwarden recommends you write down the recovery code and keep it in a safe place." + "message": "Sinu ühekordseid taastamise koode saab kasutada selleks, et lülitada kahe-astmeline sisselogimine välja juhul, kui sa oled kaotanud juurdepääsu oma kahe-astmelise sisselogimise viisidele. Bitwarden soovitab sul kirjutada üles taastamise koodid ja hoiustada neid ohutus kohas." }, "viewRecoveryCode": { "message": "Vaata taastamise koodi" @@ -2201,16 +2195,16 @@ "message": "Haldus" }, "manageCollection": { - "message": "Manage collection" + "message": "Halda kogumikku" }, "viewItems": { - "message": "View items" + "message": "Vaata kirjeid" }, "viewItemsHidePass": { "message": "View items, hidden passwords" }, "editItems": { - "message": "Edit items" + "message": "Muuda kirjeid" }, "editItemsHidePass": { "message": "Edit items, hidden passwords" @@ -2414,7 +2408,7 @@ "message": "Turvavõtme lugemisel tekkis tõrge. Proovi uuesti." }, "twoFactorWebAuthnWarning1": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." + "message": "Platvormide piirangute tõttu ei saa WebAuthn'i kõikide Bitwardeni rakendustega kasutada. Võiksid seada sisse teise kahe-astmelise kinnitamise meetodi juhuks, kui WebAuthn'i ei saa kasutada." }, "twoFactorRecoveryYourCode": { "message": "Bitwardeni kaheastmelise logimise varukood" @@ -2540,7 +2534,7 @@ "message": "Avastatud on nõrgad paroolid" }, "weakPasswordsFoundReportDesc": { - "message": "Me leidsime $COUNT$ eset sinu $VAULT$ nõrkade paroolidega. Sa peaksid vahetama need tugevamate vastu.", + "message": "Me leidsime $COUNT$ eset sinu $VAULT$st nõrkade paroolidega. Sa peaksid vahetama need tugevamate vastu.", "placeholders": { "count": { "content": "$1", @@ -2556,7 +2550,7 @@ "message": "Hoidlas olevatest kirjetest ei leitud nõrku paroole." }, "weakness": { - "message": "Weakness" + "message": "Nõrkusaste" }, "reusedPasswordsReport": { "message": "Korduvate paroolide raport" @@ -4094,7 +4088,7 @@ "message": "Review login request" }, "freeTrialEndPromptCount": { - "message": "Your free trial ends in $COUNT$ days.", + "message": "Sinu tasuta prooviaeg lõppeb $COUNT$ päeva pärast.", "placeholders": { "count": { "content": "$1", @@ -4103,7 +4097,7 @@ } }, "freeTrialEndPromptMultipleDays": { - "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "message": "$ORGANIZATION$, sinu tasuta prooviaeg lõppeb $COUNT$ päeva pärast.", "placeholders": { "count": { "content": "$2", @@ -4116,7 +4110,7 @@ } }, "freeTrialEndPromptTomorrow": { - "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "message": "$ORGANIZATION$, sinu tasuta prooviaeg lõppeb homme.", "placeholders": { "organization": { "content": "$1", @@ -4125,10 +4119,10 @@ } }, "freeTrialEndPromptTomorrowNoOrgName": { - "message": "Your free trial ends tomorrow." + "message": "Sinu tasuta prooviaeg lõppeb homme." }, "freeTrialEndPromptToday": { - "message": "$ORGANIZATION$, your free trial ends today.", + "message": "$ORGANIZATION$, sinu tasuta prooviaeg lõppeb homme.", "placeholders": { "organization": { "content": "$1", @@ -4137,16 +4131,16 @@ } }, "freeTrialEndingTodayWithoutOrgName": { - "message": "Your free trial ends today." + "message": "Sinu tasuta prooviaeg lõppeb täna." }, "clickHereToAddPaymentMethod": { - "message": "Click here to add a payment method." + "message": "Maksemeetodi lisamiseks vajuta siia." }, "joinOrganization": { "message": "Liitu organisatsiooniga" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Liitu $ORGANIZATIONNAME$ organisatsiooniga", "placeholders": { "organizationName": { "content": "$1", @@ -4188,7 +4182,7 @@ "message": "Kui sa ei pääse oma kontole ühegi kaheastmeliste kinnitamise meetodi abiga ligi, saad selle välja lülitada. Selleks kasuta kaheastmelise kinnitamise tühistamise koodi." }, "logInBelowUsingYourSingleUseRecoveryCode": { - "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + "message": "Logi allpool sisse kasutades ühekordset taastamise koodi. See lülitab sinu kontol välja kõik kahe-astmelise kinnitamise meetodid." }, "recoverAccountTwoStep": { "message": "Taasta kaheastmelise kinnitamise ligipääs" @@ -4479,10 +4473,10 @@ } }, "encryptionKeyUpdateCannotProceed": { - "message": "Encryption key update cannot proceed" + "message": "Krüpteerimise võtme uuendamisega ei õnnestunud jätkata" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "Muuda $LABEL$ lahtrit", "placeholders": { "label": { "content": "$1", @@ -4491,7 +4485,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "Paiguta $LABEL$ ümber. Kasuta nooli, et liigutada lahtrit üles või alla.", "placeholders": { "label": { "content": "$1", @@ -4500,7 +4494,7 @@ } }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ üles liigutatud, $INDEX$. kohale $LENGTH$-st", "placeholders": { "label": { "content": "$1", @@ -4517,7 +4511,7 @@ } }, "reorderFieldDown": { - "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ alla liigutatud, $INDEX$. kohale $LENGTH$-st", "placeholders": { "label": { "content": "$1", @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Loo parool" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -7286,16 +7301,16 @@ "message": "Launch Duo" }, "turnOn": { - "message": "Turn on" + "message": "Lülita sisse" }, "on": { - "message": "On" + "message": "Sees" }, "off": { - "message": "Off" + "message": "Väljas" }, "members": { - "message": "Members" + "message": "Liikmed" }, "reporting": { "message": "Reporting" @@ -7316,46 +7331,46 @@ "message": "Bright Blue" }, "green": { - "message": "Green" + "message": "Roheline" }, "orange": { - "message": "Orange" + "message": "Oranž" }, "lavender": { - "message": "Lavender" + "message": "Lavendel" }, "yellow": { - "message": "Yellow" + "message": "Kollane" }, "indigo": { "message": "Indigo" }, "teal": { - "message": "Teal" + "message": "Sinakasroheline" }, "salmon": { - "message": "Salmon" + "message": "Lõhe" }, "pink": { - "message": "Pink" + "message": "Roosa" }, "customColor": { - "message": "Custom Color" + "message": "Kohandatud Värv" }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- Vali --" }, "multiSelectPlaceholder": { - "message": "-- Type to filter --" + "message": "-- Filtreerimiseks trüki siia --" }, "multiSelectLoading": { - "message": "Retrieving options..." + "message": "Valikute hankimine..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "Ühtki kirjet ei leitud" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "Tühjenda kõik" }, "toggleCharacterCount": { "message": "Toggle character count", @@ -7366,14 +7381,14 @@ "description": "'Character count' describes a feature that displays a number next to each character of the password." }, "hide": { - "message": "Hide" + "message": "Peida" }, "projects": { - "message": "Projects", + "message": "Projektid", "description": "Description for the Projects field." }, "lastEdited": { - "message": "Last edited", + "message": "Viimati muudetud", "description": "The label for the date and time when a item was last edited." }, "editSecret": { @@ -7870,7 +7885,7 @@ } }, "domainNameTh": { - "message": "Name" + "message": "Nimi" }, "domainStatusTh": { "message": "Status" @@ -7978,7 +7993,7 @@ "message": "Switch products" }, "freeOrgInvLimitReachedManageBilling": { - "message": "Free organizations may have up to $SEATCOUNT$ members. Upgrade to a paid plan to invite more members.", + "message": "Tasuta organisatsioonidel võib olla kuni $SEATCOUNT$ liiget. Hangi tasuline plaan, et kutsuda rohkem liikmeid.", "placeholders": { "seatcount": { "content": "$1", @@ -7987,7 +8002,7 @@ } }, "freeOrgInvLimitReachedNoManageBilling": { - "message": "Free organizations may have up to $SEATCOUNT$ members. Contact your organization owner to upgrade.", + "message": "Tasuta organisatsioonidel võib olla kuni $SEATCOUNT$ liiget. Selle arvu tõstmiseks kontakteeru selle organisatsiooni omanikuga.", "placeholders": { "seatcount": { "content": "$1", @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index b587e7f1611..5668d987b69 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -547,12 +547,6 @@ "message": "Hondoa jo", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Sortu pasahitza" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Egiaztatu pasahitza konprometituta dagoen." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Sortu pasahitza" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index a7c07192acc..b8a71e7b971 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -24,7 +24,7 @@ "message": "کلمات عبور در معرض خطر (ضعیف، افشا شده یا تکراری) را در برنامه‌ها بررسی کنید. برنامه‌های حیاتی خود را انتخاب کنید تا اقدامات امنیتی را برای کاربران‌تان اولویت‌بندی کنید و به کلمات عبور در معرض خطر رسیدگی کنید." }, "dataLastUpdated": { - "message": "آخرین به‌روزرسانی داده‌ها: \\$DATE\\$", + "message": "آخرین به‌روزرسانی داده‌ها: $DATE$", "placeholders": { "date": { "content": "$1", @@ -66,7 +66,7 @@ } }, "notifiedMembersWithCount": { - "message": "Notified members ($COUNT$)", + "message": "اعضا مطلع شدند ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -84,25 +84,25 @@ } }, "noAppsInOrgDescription": { - "message": "As users save logins, applications appear here, showing any at-risk passwords. Mark critical apps and notify users to update passwords." + "message": "با ذخیره‌ی اطلاعات ورود توسط کاربران، برنامه‌ها در اینجا نمایش داده می‌شوند و کلمات عبور در معرض خطر مشخص می‌گردند. برنامه‌های حیاتی را علامت‌گذاری کرده و کاربران را برای به‌روزرسانی کلمات عبور مطلع کنید." }, "noCriticalAppsTitle": { - "message": "You haven't marked any applications as a Critical" + "message": "شما هیچ برنامه‌ای را به عنوان حیاتی علامت‌گذاری نکرده‌اید" }, "noCriticalAppsDescription": { - "message": "Select your most critical applications to discover at-risk passwords, and notify users to change those passwords." + "message": "مهم‌ترین برنامه‌های خود را انتخاب کنید تا کلمات عبور در معرض خطر شناسایی شوند و کاربران برای تغییر آن‌ها مطلع شوند." }, "markCriticalApps": { - "message": "Mark critical apps" + "message": "برنامه‌های حیاتی را علامت‌گذاری کنید" }, "markAppAsCritical": { - "message": "Mark app as critical" + "message": "برنامه را به عنوان حیاتی علامت‌گذاری کنید" }, "applicationsMarkedAsCriticalSuccess": { - "message": "Applications marked as critical" + "message": "برنامه‌های علامت گذاری شده به عنوان حیاتی" }, "application": { - "message": "اپلیکیشن" + "message": "برنامه" }, "atRiskPasswords": { "message": "کلمات عبور در معرض خطر" @@ -111,16 +111,16 @@ "message": "درخواست تغییر کلمه عبور" }, "totalPasswords": { - "message": "Total passwords" + "message": "تمام کلمات عبور" }, "searchApps": { - "message": "Search applications" + "message": "برنامه‌ها را جستجو کنید" }, "atRiskMembers": { - "message": "At-risk members" + "message": "اعضای در معرض خطر" }, "atRiskMembersWithCount": { - "message": "At-risk members ($COUNT$)", + "message": "اعضای در معرض خطر ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -129,7 +129,7 @@ } }, "atRiskApplicationsWithCount": { - "message": "At-risk applications ($COUNT$)", + "message": "برنامه‌های در معرض خطر ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -138,19 +138,19 @@ } }, "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "message": "این اعضا با کلمات عبور ضعیف، افشا شده یا تکراری وارد برنامه‌ها می‌شوند." }, "atRiskMembersDescriptionNone": { - "message": "These are no members logging into applications with weak, exposed, or reused passwords." + "message": "هیچ عضوی با کلمات عبور ضعیف، افشا شده یا تکراری وارد برنامه‌ها نمی‌شود." }, "atRiskApplicationsDescription": { - "message": "These applications have weak, exposed, or reused passwords." + "message": "این برنامه‌ها دارای کلمات عبور ضعیف، افشا شده یا تکراری هستند." }, "atRiskApplicationsDescriptionNone": { - "message": "These are no applications with weak, exposed, or reused passwords." + "message": "هیچ برنامه‌ای با کلمات عبور ضعیف، افشا شده یا تکراری وجود ندارد." }, "atRiskMembersDescriptionWithApp": { - "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "message": "این اعضا با کلمات عبور ضعیف، افشا شده یا تکراری وارد برنامه‌ی $APPNAME$ می‌شوند.", "placeholders": { "appname": { "content": "$1", @@ -159,7 +159,7 @@ } }, "atRiskMembersDescriptionWithAppNone": { - "message": "There are no at risk members for $APPNAME$.", + "message": "هیچ عضو در معرض خطری برای $APPNAME$ وجود ندارد.", "placeholders": { "appname": { "content": "$1", @@ -168,19 +168,19 @@ } }, "totalMembers": { - "message": "Total members" + "message": "کل اعضا" }, "atRiskApplications": { - "message": "At-risk applications" + "message": "برنامه‌های در معرض خطر" }, "totalApplications": { - "message": "Total applications" + "message": "کل برنامه‌ها" }, "unmarkAsCriticalApp": { - "message": "Unmark as critical app" + "message": "لغو علامت حیاتی بودن برنامه" }, "criticalApplicationSuccessfullyUnmarked": { - "message": "Critical application successfully unmarked" + "message": "برنامه حیاتی با موفقیت لغو علامت شد" }, "whatTypeOfItem": { "message": "این چه نوع موردی است؟" @@ -247,7 +247,7 @@ "message": "جزئیات کارت" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "$BRAND$ جزئیات", "placeholders": { "brand": { "content": "$1", @@ -256,19 +256,19 @@ } }, "itemHistory": { - "message": "Item history" + "message": "تاریخچه مورد" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "کلید احراز هویت" }, "autofillOptions": { - "message": "Autofill options" + "message": "گزینه‌های پر کردن خودکار" }, "websiteUri": { - "message": "Website (URI)" + "message": "وب‌سایت (نشانی اینترنتی)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "وب‌سایت (نشانی اینترنتی) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -278,16 +278,16 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "وب‌سایت اضافه شد" }, "addWebsite": { - "message": "Add website" + "message": "افزودن وب‌سایت" }, "deleteWebsite": { - "message": "Delete website" + "message": "حذف وب‌سایت" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "پیش‌فرض ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -297,7 +297,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "نمایش تطبیق سایت $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -306,7 +306,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "مخفی کردن تطبیق سایت $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -315,7 +315,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "پر کردن خودکار هنگام بارگذاری صفحه؟" }, "number": { "message": "شماره" @@ -330,7 +330,7 @@ "message": "کد امنیتی (cvv)" }, "securityCodeSlashCVV": { - "message": "Security code / CVV" + "message": "کد امنیتی / CVV" }, "identityName": { "message": "نام هویت" @@ -408,10 +408,10 @@ "message": "دکتر" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "تاریخ کارت منقضی شده است" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "اگر تمدید کرده‌اید، اطلاعات کارت را به‌روزرسانی کنید" }, "expirationMonth": { "message": "ماه انقضاء" @@ -423,16 +423,16 @@ "message": "کلید احراز هویت (TOTP)" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "تأیید دو مرحله‌ای را بدون دردسر کنید" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden می‌تواند کدهای تأیید دو مرحله‌ای را ذخیره و پر کند. کلید را کپی کرده و در این فیلد قرار دهید." }, "totpHelperWithCapture": { - "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + "message": "Bitwarden می‌تواند کدهای تأیید دو مرحله‌ای را ذخیره و پر کند. برای اسکن کد QR احراز هویت کننده این وب‌سایت، روی آیکون دوربین کلیک کنید یا کلید را کپی کرده و در این فیلد قرار دهید." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "درباره احراز هویت کننده‌ها بیشتر بدانید" }, "folder": { "message": "پوشه" @@ -483,7 +483,7 @@ "message": "ويرايش پوشه" }, "editWithName": { - "message": "Edit $ITEM$: $NAME$", + "message": "ویرایش $ITEM$: $NAME$", "placeholders": { "item": { "content": "$1", @@ -499,13 +499,13 @@ "message": "پوشه جدید" }, "folderName": { - "message": "Folder name" + "message": "نام پوشه" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "برای تو در تو کردن یک پوشه، نام پوشه والد را وارد کرده و سپس یک “/” اضافه کنید. مثال: Social/Forums" }, "deleteFolderPermanently": { - "message": "Are you sure you want to permanently delete this folder?" + "message": "مطمئنید می‌خواهید این پوشه را برای همیشه پاک کنید؟" }, "baseDomain": { "message": "دامنه پایه", @@ -547,12 +547,6 @@ "message": "باز و بسته کردن", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "تولید کلمه عبور" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "بررسی کنید که آیا کلمه عبور افشا شده است." }, @@ -654,7 +648,7 @@ "message": "یادداشت امن" }, "typeSshKey": { - "message": "SSH key" + "message": "کلید SSH" }, "typeLoginPlural": { "message": "ورودها" @@ -1013,7 +1007,7 @@ "message": "خارج شد" }, "loggedOutDesc": { - "message": "شما از اکانت خود خارج شده‌اید." + "message": "شما از حساب خود خارج شده‌اید." }, "loginExpired": { "message": "نشست ورود شما منقضی شده است." @@ -1064,13 +1058,13 @@ "message": "با کلمه عبور اصلی وارد شوید" }, "readingPasskeyLoading": { - "message": "خواندن کلید عبور..." + "message": "در حال خواندن کلید عبور..." }, "readingPasskeyLoadingInfo": { "message": "این پنجره را باز نگه دارید و دستورهای مرورگر خود را دنبال کنید." }, "useADifferentLogInMethod": { - "message": "Use a different log in method" + "message": "از روش ورود متفاوتی استفاده کنید" }, "logInWithPasskey": { "message": "با کلید عبور وارد شوید" @@ -1082,10 +1076,10 @@ "message": "خوش آمدید" }, "invalidPasskeyPleaseTryAgain": { - "message": "Invalid Passkey. Please try again." + "message": "کلید عبور نامعتبر است. لطفاً دوباره تلاش کنید." }, "twoFactorForPasskeysNotSupportedOnClientUpdateToLogIn": { - "message": "2FA for passkeys is not supported. Update the app to log in." + "message": "احراز هویت دو مرحله‌ای برای کلیدهای عبور پشتیبانی نمی‌شود. برای ورود، برنامه را به‌روزرسانی کنید." }, "loginWithPasskeyInfo": { "message": "از یک کلمه عبور ایجاد شده استفاده کنید که به طور خودکار بدون کلمه عبور شما را وارد می‌کند. بیومتریک‌ها، مانند تشخیص چهره یا اثر انگشت، یا سایر روش‌های امنیتی FIDO2 هویت شما را تأیید می‌کنند." @@ -1115,22 +1109,22 @@ "message": "کلید عبور خود را برای کمک به شناسایی آن نام ببرید." }, "useForVaultEncryption": { - "message": "Use for vault encryption" + "message": "برای رمزگذاری گاوصندوق استفاده شود" }, "useForVaultEncryptionInfo": { - "message": "Log in and unlock on supported devices without your master password. Follow the prompts from your browser to finalize setup." + "message": "بدون نیاز به کلمه عبور اصلی، در دستگاه‌های پشتیبانی‌شده وارد شوید و قفل را باز کنید. برای تکمیل تنظیمات، دستورالعمل‌های مرورگر خود را دنبال کنید." }, "useForVaultEncryptionErrorReadingPasskey": { - "message": "Error reading passkey. Try again or uncheck this option." + "message": "خطا در خواندن کلید عبور. دوباره تلاش کنید یا این گزینه را غیرفعال کنید." }, "encryptionNotSupported": { "message": "رمزگذاری پشتیبانی نمی‌شود" }, "enablePasskeyEncryption": { - "message": "Set up encryption" + "message": "راه‌اندازی رمزگذاری" }, "usedForEncryption": { - "message": "Used for encryption" + "message": "برای رمزنگاری استفاده شد" }, "loginWithPasskeyEnabled": { "message": "با فعال بودن کلید ورود وارد شوید" @@ -1187,7 +1181,7 @@ "message": "کدی را که به ایمیل شما ارسال شده وارد کنید" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "کد سامانه تأیید کننده را وارد نمایید" + "message": "کد را از برنامه احراز هویت خود وارد کنید" }, "pressYourYubiKeyToAuthenticate": { "message": "برای احراز هویت، کلید YubiKey خود را فشار دهید" @@ -1199,25 +1193,25 @@ "message": "نشست احراز هویت منقضی شد. لطفاً فرایند ورود را دوباره شروع کنید." }, "verifyYourIdentity": { - "message": "Verify your Identity" + "message": "هویت خود را تأیید کنید" }, "weDontRecognizeThisDevice": { - "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + "message": "ما این دستگاه را نمی‌شناسیم. برای تأیید هویت خود، کدی را که به ایمیلتان ارسال شده وارد کنید." }, "continueLoggingIn": { - "message": "Continue logging in" + "message": "ادامه ورود" }, "whatIsADevice": { - "message": "What is a device?" + "message": "دستگاه چیست؟" }, "aDeviceIs": { - "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + "message": "دستگاه، یک نصب منحصربه‌فرد از اپلیکیشن Bitwarden است که در آن وارد حساب کاربری خود شده‌اید. نصب مجدد، پاک کردن داده‌های برنامه یا پاک کردن کوکی‌ها ممکن است باعث شود یک دستگاه چند بار ظاهر شود." }, "logInInitiated": { "message": "ورود به سیستم آغاز شد" }, "logInRequestSent": { - "message": "Request sent" + "message": "درخواست ارسال شد" }, "submit": { "message": "ثبت" @@ -1250,13 +1244,13 @@ "message": "یادآور کلمه عبور اصلی (اختیاری)" }, "newMasterPassHint": { - "message": "New master password hint (optional)" + "message": "راهنمای کلمه عبور اصلی جدید (اختیاری)" }, "masterPassHintLabel": { "message": "یادآور کلمه عبور اصلی" }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "اگر کلمه عبور خود را فراموش کنید، یادآور کلمه عبور می‌تواند به ایمیل شما ارسال شود. حداکثر $CURRENT$/$MAXIMUM$ کاراکتر.", "placeholders": { "current": { "content": "$1", @@ -1272,16 +1266,16 @@ "message": "تنظیمات" }, "accountEmail": { - "message": "Account email" + "message": "حساب ایمیل" }, "requestHint": { - "message": "Request hint" + "message": "درخواست راهنمایی" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "درخواست یادآور کلمه عبور" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" + "message": "نشانی ایمیل حساب کاربری خود را وارد کنید تا راهنمای کلمه عبور برای شما ارسال شود" }, "getMasterPasswordHint": { "message": "دریافت یادآور کلمه عبور اصلی" @@ -1315,10 +1309,10 @@ "message": "حساب کاربری جدید شما ساخته شد! حالا می‌توانید وارد شوید." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "حساب کاربری جدید شما ایجاد شده است!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "شما با موفقیت وارد شدید!" }, "trialAccountCreated": { "message": "حساب کاربری با موفقیت ساخته شد." @@ -1336,10 +1330,10 @@ "message": "نشانی ایمیل" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "گاوصندوق‌تان قفل شد" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "حساب کاربری شما قفل شده است" }, "uuid": { "message": "UUID" @@ -1376,7 +1370,7 @@ "message": "شما اجازه مشاهده همه موارد در این مجموعه را ندارید." }, "youDoNotHavePermissions": { - "message": "You do not have permissions to this collection" + "message": "شما اجازه دسترسی به این مجموعه را ندارید" }, "noCollectionsInList": { "message": "هیچ مجموعه ای برای لیست کردن وجود ندارد." @@ -1403,13 +1397,13 @@ "message": "یک اعلان به دستگاه شما ارسال شده است." }, "notificationSentDevicePart1": { - "message": "Unlock Bitwarden on your device or on the " + "message": "قفل Bitwarden را روی دستگاه خود باز کنید یا روی " }, "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" + "message": "آیا در تلاش برای دسترسی به حساب کاربری خود هستید؟" }, "accessAttemptBy": { - "message": "Access attempt by $EMAIL$", + "message": "تلاش برای دسترسی به سیستم توسط $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -1418,22 +1412,22 @@ } }, "confirmAccess": { - "message": "Confirm access" + "message": "تأیید دسترسی" }, "denyAccess": { - "message": "Deny access" + "message": "دسترسی را رد کن" }, "notificationSentDeviceAnchor": { - "message": "web app" + "message": "برنامه وب" }, "notificationSentDevicePart2": { - "message": "Make sure the Fingerprint phrase matches the one below before approving." + "message": "اطمینان حاصل کنید که عبارت اثر انگشت با عبارت زیر مطابقت دارد قبل از تأیید." }, "notificationSentDeviceComplete": { - "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." + "message": "Bitwarden را در دستگاه خود باز کنید. پیش از تأیید، اطمینان حاصل کنید که عبارت اثر انگشت با عبارت زیر مطابقت دارد." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "یک اعلان به دستگاه شما ارسال شده است" }, "versionNumber": { "message": "نسخه $VERSION_NUMBER$", @@ -1454,14 +1448,14 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "در این دستگاه به مدت ۳۰ روز دوباره نپرس" }, "selectAnotherMethod": { - "message": "Select another method", + "message": "انتخاب روش دیگر", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "از کد بازیابی‌تان استفاده کنید" }, "insertU2f": { "message": "کلید امنیتی خود را وارد پورت USB رایانه کنید، اگر دکمه ای دارد آن را بفشارید." @@ -1479,7 +1473,7 @@ "message": "گزینه‌های ورود دو مرحله‌ای" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "انتخاب ورود دو مرحله‌ای" }, "recoveryCodeDesc": { "message": "دسترسی به تمامی ارائه‌دهندگان دو مرحله‌ای را از دست داده‌اید؟ از کد بازیابی خود برای غیرفعال‌سازی ارائه‌دهندگان دو مرحله‌ای از حسابتان استفاده کنید." @@ -1491,17 +1485,17 @@ "message": "برنامه احراز هویت" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "کدی را وارد کنید که توسط یک برنامه احراز هویت مانند Bitwarden Authenticator تولید شده است.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "کلید امنیت Yubico OTP" }, "yubiKeyDesc": { "message": "از یک YubiKey برای دسترسی به حسابتان استفاده کنید. با دستگاه های YubiKey سری 4، سری 5 و NEO کار می‌کند." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "کدی را وارد کنید که توسط Duo Security تولید شده است.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -1515,22 +1509,22 @@ "message": "کلید امنیتی FIDO U2F" }, "webAuthnTitle": { - "message": "FIDO2 WebAuthn" + "message": "کلید عبور" }, "webAuthnDesc": { - "message": "برای دسترسی به حساب خود از هر کلید امنیتی فعال شده WebAuthn استفاده کنید." + "message": "برای دسترسی به حساب کاربری خود از هر کلید امنیتی فعال شده WebAuthn استفاده کنید." }, "webAuthnMigrated": { "message": "(مهاجرت از FIDO)" }, "openInNewTab": { - "message": "Open in new tab" + "message": "گشودن در زبانهٔ جدید" }, "emailTitle": { "message": "ایمیل" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "کدی را که به ایمیل شما ارسال شده وارد کنید." }, "continue": { "message": "ادامه" @@ -1569,7 +1563,7 @@ "message": "آیا اطمینان دارید که می‌خواهید ادامه دهید؟" }, "moveSelectedItemsDesc": { - "message": "Choose a folder that you would like to add the $COUNT$ selected item(s) to.", + "message": "پوشه ای را انتخاب کنید که می‌خواهید $COUNT$ مورد انتخاب شده را به آن اضافه کنید.", "placeholders": { "count": { "content": "$1", @@ -1587,10 +1581,10 @@ "message": "کپی UUID" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "خطای به‌روزرسانی توکن دسترسی" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "هیچ توکن به‌روزرسانی یا کلید API یافت نشد. لطفاً از حساب کاربری خود خارج شده و دوباره وارد شوید." }, "warning": { "message": "هشدار" @@ -1617,7 +1611,7 @@ "message": "برون ریزی" }, "exportFrom": { - "message": "Export from" + "message": "برون ریزی از" }, "exportVault": { "message": "برون ریزی گاوصندوق" @@ -1691,14 +1685,14 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "از کاراکترهای مبهم خودداری کن", "description": "Label for the avoid ambiguous characters checkbox." }, "length": { "message": "طول" }, "passwordMinLength": { - "message": "Minimum password length" + "message": "حداقل طول کلمه عبور" }, "uppercase": { "message": "حروف بزرگ (A-Z)", @@ -1729,7 +1723,7 @@ "message": "شامل عدد" }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "نیازمندی‌های سیاست سازمانی بر گزینه‌های تولید کننده شما اعمال شده‌اند.", "description": "Indicates that a policy limits the credential generator screen." }, "passwordHistory": { @@ -1876,31 +1870,31 @@ "message": "ادامه دادن همچنین شما را از نشست فعلی خود خارج می‌کند و باید دوباره وارد سیستم شوید. در صورت راه اندازی مجدداً از شما خواسته می‌شود تا دوباره به سیستم دو مرحله ای بپردازید. جلسات فعال در دستگاه‌های دیگر ممکن است تا یک ساعت فعال بمانند." }, "newDeviceLoginProtection": { - "message": "New device login" + "message": "ورود از دستگاه جدید" }, "turnOffNewDeviceLoginProtection": { - "message": "Turn off new device login protection" + "message": "محافظت از ورود دستگاه جدید را غیرفعال کنید" }, "turnOnNewDeviceLoginProtection": { - "message": "Turn on new device login protection" + "message": "محافظت از ورود دستگاه جدید را فعال کنید" }, "turnOffNewDeviceLoginProtectionModalDesc": { - "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device." + "message": "برای غیرفعال‌سازی ایمیل‌های تأییدیه‌ای که Bitwarden هنگام ورود از دستگاه جدید ارسال می‌کند، از بخش زیر ادامه دهید." }, "turnOnNewDeviceLoginProtectionModalDesc": { - "message": "Proceed below to have bitwarden send you verification emails when you login from a new device." + "message": "برای فعال‌سازی ارسال ایمیل‌های تأیید هنگام ورود از دستگاه جدید توسط Bitwarden، از بخش زیر ادامه دهید." }, "turnOffNewDeviceLoginProtectionWarning": { - "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login." + "message": "با غیرفعال بودن محافظت از ورود دستگاه جدید، هر کسی که کلمه عبور اصلی شما را داشته باشد می‌تواند از هر دستگاهی به حسابتان دسترسی پیدا کند. برای محافظت از حساب کاربری بدون استفاده از ایمیل‌های تأیید، ورود دو مرحله‌ای را فعال کنید." }, "accountNewDeviceLoginProtectionSaved": { - "message": "New device login protection changes saved" + "message": "تغییرات مربوط به محافظت ورود دستگاه جدید ذخیره شد" }, "sessionsDeauthorized": { "message": "همه نشست‌ها غیرمجاز است" }, "accountIsOwnedMessage": { - "message": "This account is owned by $ORGANIZATIONNAME$", + "message": "این حساب کاربری متعلق به $ORGANIZATIONNAME$ است", "placeholders": { "organizationName": { "content": "$1", @@ -1945,7 +1939,7 @@ "message": "حساب شما بسته شد و تمام داده های مرتبط حذف شده است." }, "deleteOrganizationWarning": { - "message": "Deleting your organization is permanent. It cannot be undone." + "message": "حذف سازمان شما دائمی است و قابل بازگشت نیست." }, "myAccount": { "message": "حساب من" @@ -1957,7 +1951,7 @@ "message": "درون ریزی داده" }, "onboardingImportDataDetailsPartOne": { - "message": "If you don't have any data to import, you can create a ", + "message": "اگر داده‌ای برای وارد کردن ندارید، می‌توانید یک ", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsLink": { @@ -1969,11 +1963,11 @@ "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new login instead. (Optional second half: You may need to wait until your administrator confirms your organization membership.)" }, "onboardingImportDataDetailsPartTwoNoOrgs": { - "message": " instead.", + "message": " به جای آن ایجاد کنید.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead." }, "onboardingImportDataDetailsPartTwoWithOrgs": { - "message": " instead. You may need to wait until your administrator confirms your organization membership.", + "message": " به جای آن. ممکن است لازم باشد تا مدیر شما عضویت‌تان در سازمان را تأیید کند.", "description": "This will be part of a larger sentence, that will read like this: If you don't have any data to import, you can create a new item instead. You may need to wait until your administrator confirms your organization membership." }, "importError": { @@ -2160,7 +2154,7 @@ "message": "راه‌اندازی ورود دو مرحله‌ای می‌تواند برای همیشه حساب Bitwarden شما را قفل کند. یک کد بازیابی به شما امکان می‌دهد در صورتی که دیگر نمی‌توانید از ارائه‌دهنده‌ی ورود دو مرحله‌ای معمولی خود استفاده کنید (به عنوان مثال: دستگاه خود را گم می‌کنید) به حساب خود دسترسی پیدا کنید. اگر دسترسی به حساب خود را از دست بدهید، پشتیبانی Bitwarden نمی‌تواند به شما کمک کند. توصیه می‌کنیم کد بازیابی را یادداشت یا چاپ کنید و آن را در مکانی امن نگهداری کنید." }, "yourSingleUseRecoveryCode": { - "message": "Your single-use recovery code can be used to turn off two-step login in the event that you lose access to your two-step login provider. Bitwarden recommends you write down the recovery code and keep it in a safe place." + "message": "کد بازیابی یک‌بار مصرف شما می‌تواند در صورت از دست دادن دسترسی به سرویس ورود دو مرحله‌ای، برای غیرفعال کردن آن استفاده شود. Bitwarden توصیه می‌کند این کد را یادداشت کرده و در جای امنی نگهداری کنید." }, "viewRecoveryCode": { "message": "نمایش کد بازیابی" @@ -2207,13 +2201,13 @@ "message": "مشاهده موارد" }, "viewItemsHidePass": { - "message": "View items, hidden passwords" + "message": "مشاهده موارد، کلمه عبور مخفی" }, "editItems": { "message": "ویرایش موارد" }, "editItemsHidePass": { - "message": "Edit items, hidden passwords" + "message": "ویرایش موارد، کلمه عبور مخفی" }, "disable": { "message": "خاموش کردن" @@ -2231,10 +2225,10 @@ "message": "کلمه عبور اصلی خود را برای تغییر تنظیمات ورود به سیستم دو مرحله ای وارد کنید." }, "twoStepAuthenticatorInstructionPrefix": { - "message": "Download an authenticator app such as" + "message": "یک برنامه احراز هویت بارگیری کنید مانند" }, "twoStepAuthenticatorInstructionInfix1": { - "message": "," + "message": "،" }, "twoStepAuthenticatorInstructionInfix2": { "message": "یا" @@ -2243,7 +2237,7 @@ "message": "." }, "continueToExternalUrlTitle": { - "message": "Continue to $URL$?", + "message": "ادامه به $URL$؟", "placeholders": { "url": { "content": "$1", @@ -2252,7 +2246,7 @@ } }, "continueToExternalUrlDesc": { - "message": "You are leaving Bitwarden and launching an external website in a new window." + "message": "شما در حال ترک Bitwarden و باز کردن یک وب‌سایت خارجی در پنجره جدید هستید." }, "twoStepContinueToBitwardenUrlTitle": { "message": "به bitwarden.com ادامه می‌دهید؟" @@ -2261,10 +2255,10 @@ "message": "احراز هویت کننده Bitwarden به شما امکان می‌دهد کلیدهای احراز هویت را ذخیره کرده و کدهای TOTP را برای فرآیندهای تأیید دومرحله‌ای تولید کنید. برای اطلاعات بیشتر به وب‌سایت bitwarden.com مراجعه کنید." }, "twoStepAuthenticatorScanCodeV2": { - "message": "Scan the QR code below with your authenticator app or enter the key." + "message": "کد QR زیر را با برنامه احراز هویت خود اسکن کنید یا کلید را وارد کنید." }, "twoStepAuthenticatorQRCanvasError": { - "message": "Could not load QR code. Try again or use the key below." + "message": "بارگذاری کد QR امکان‌پذیر نیست. دوباره تلاش کنید یا از کلید زیر استفاده کنید." }, "key": { "message": "کلید" @@ -2354,7 +2348,7 @@ "message": "شناسه کاربر" }, "twoFactorDuoClientSecret": { - "message": "Client Secret" + "message": "رمز مخفی مشتری" }, "twoFactorDuoApiHostname": { "message": "نام میزبان API" @@ -2414,7 +2408,7 @@ "message": "مشکلی در خواندن کلید امنیتی وجود داشت. دوباره امتحان کنید." }, "twoFactorWebAuthnWarning1": { - "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used." + "message": "به‌دلیل محدودیت‌های پلتفرم، WebAuthn در تمام برنامه‌های Bitwarden قابل استفاده نیست. بهتر است یک روش ورود دو مرحله‌ای دیگر نیز تنظیم کنید تا در مواقعی که WebAuthn قابل استفاده نیست، بتوانید به حساب کاربرب خود دسترسی داشته باشید." }, "twoFactorRecoveryYourCode": { "message": "کد بازیابی ورود دو مرحله ای Bitwarden شما" @@ -2447,7 +2441,7 @@ "message": "وب‌سایت های نا امن پیدا شد" }, "unsecuredWebsitesFoundReportDesc": { - "message": "We found $COUNT$ items in your $VAULT$ with unsecured URIs. You should change their URI scheme to https:// if the website allows it.", + "message": "ما $COUNT$ مورد در $VAULT$ شما یافتیم که نشانی اینترنتی آن‌ها نا امن است. در صورت امکان، باید طرح نشانی اینترنتی آن‌ها را به https:// تغییر دهید.", "placeholders": { "count": { "content": "$1", @@ -2472,7 +2466,7 @@ "message": "ورود‌های بدون ورود دو مرحله ای یافت شد" }, "inactive2faFoundReportDesc": { - "message": "We found $COUNT$ website(s) in your $VAULT$ that may not be configured with two-step login (according to 2fa.directory). To further protect these accounts, you should set up two-step login.", + "message": "ما $COUNT$ وب‌سایت در $VAULT$ شما یافتیم که ممکن است ورود دو مرحله‌ای برای آن‌ها فعال نباشد (بر اساس اطلاعات 2fa.directory). برای محافظت بیشتر از این حساب‌ها، بهتر است ورود دو مرحله‌ای را فعال کنید.", "placeholders": { "count": { "content": "$1", @@ -2500,7 +2494,7 @@ "message": "کلمه‌های عبور افشا شده یافت شد" }, "exposedPasswordsFoundReportDesc": { - "message": "We found $COUNT$ items in your $VAULT$ that have passwords that were exposed in known data breaches. You should change them to use a new password.", + "message": "ما $COUNT$ مورد در $VAULT$ شما یافتیم که کلمات عبور آن‌ها در رخنه‌های داده‌ای شناخته‌شده افشا شده‌اند. شما باید این کلمات عبور را به کلمات عبور جدیدی تغییر دهید.", "placeholders": { "count": { "content": "$1", @@ -2519,7 +2513,7 @@ "message": "کلمه‌های عبور افشا شده را بررسی کنید" }, "timesExposed": { - "message": "Times exposed" + "message": "تعداد دفعات افشا" }, "exposedXTimes": { "message": "$COUNT$ بار در معرض نمایش قرار گرفت", @@ -2540,7 +2534,7 @@ "message": "کلمه‌های عبور ضعیف پیدا شد" }, "weakPasswordsFoundReportDesc": { - "message": "We found $COUNT$ items in your $VAULT$ with passwords that are not strong. You should update them to use stronger passwords.", + "message": "ما $COUNT$ مورد در $VAULT$ شما یافتیم که کلمات عبور آن‌ها قوی نیستند. بهتر است آن‌ها را با کلمات عبور قوی‌تری به‌روزرسانی کنید.", "placeholders": { "count": { "content": "$1", @@ -2556,7 +2550,7 @@ "message": "هیچ موردی در گاوصندوق شما کلمه‌ی عبور ضعیفی ندارد." }, "weakness": { - "message": "Weakness" + "message": "ضعف" }, "reusedPasswordsReport": { "message": "کلمه‌های عبور مجدد استفاده شده" @@ -2568,7 +2562,7 @@ "message": "کلمه‌های عبور مجدد استفاده شده یافت شد" }, "reusedPasswordsFoundReportDesc": { - "message": "We found $COUNT$ passwords that are being reused in your $VAULT$. You should change them to a unique value.", + "message": "ما $COUNT$ کلمه عبور در $VAULT$ شما یافتیم که به‌صورت تکراری استفاده شده‌اند. بهتر است آن‌ها را به مقادیر منحصربه‌فردی تغییر دهید.", "placeholders": { "count": { "content": "$1", @@ -2584,7 +2578,7 @@ "message": "هیچ ورودی در گاوصندوق شما کلمه عبوری ندارد که مجدداً مورد استفاده قرار می‌گیرد." }, "timesReused": { - "message": "Times reused" + "message": "تعداد دفعات استفاده مجدد" }, "reusedXTimes": { "message": "$COUNT$ بار دوباره استفاده شد", @@ -2745,7 +2739,7 @@ "message": "طرح خانواده Bitwarden." }, "addons": { - "message": "افزودنی ها" + "message": "افزونه‌ها" }, "premiumAccess": { "message": "دسترسی پرمیوم" @@ -2884,7 +2878,7 @@ "message": "دانلود مجوز" }, "viewBillingToken": { - "message": "View Billing Token" + "message": "مشاهده توکن صورتحساب" }, "updateLicense": { "message": "به‌روزرسانی مجوز" @@ -2933,10 +2927,10 @@ "message": "صورت حساب‌ها" }, "noUnpaidInvoices": { - "message": "No unpaid invoices." + "message": "هیچ صورتحساب پرداخت‌نشده‌ای وجود ندارد." }, "noPaidInvoices": { - "message": "No paid invoices." + "message": "هیچ صورتحساب پرداخت‌شده‌ای وجود ندارد." }, "paid": { "message": "پرداخت شد", @@ -2995,7 +2989,7 @@ "message": "با پشتیبانی مشتری تماس بگیرید" }, "contactSupportShort": { - "message": "Contact Support" + "message": "تماس با پشتیبانی" }, "updatedPaymentMethod": { "message": "روش پرداخت به‌روز شده." @@ -3099,7 +3093,7 @@ "message": "برای مشاغل و سایر سازمان های تیمی." }, "planNameTeamsStarter": { - "message": "Teams Starter" + "message": "طرح ابتدایی تیم‌ها" }, "planNameEnterprise": { "message": "سازمانی" @@ -3174,7 +3168,7 @@ } }, "onPremHostingOptional": { - "message": "میزبانی داخلی (اختیاری)" + "message": "میزبانی در محل (اختیاری)" }, "usersGetPremium": { "message": "کاربران به ویژگی‌های پرمیوم دسترسی پیدا می‌کنند" @@ -3213,7 +3207,7 @@ } }, "trialSecretsManagerThankYou": { - "message": "Thanks for signing up for Bitwarden Secrets Manager for $PLAN$!", + "message": "از ثبت‌نام شما برای بتا مدیر اسرار Bitwarden در طرح $PLAN$ سپاسگزاریم!", "placeholders": { "plan": { "content": "$1", @@ -3333,10 +3327,10 @@ "message": "شناسه خارجی می‌تواند به عنوان یک مرجع یا برای پیوند دادن این منبع به یک سیستم خارجی مانند فهرست کاربری استفاده شود." }, "ssoExternalId": { - "message": "SSO External ID" + "message": "شناسه خارجی SSO" }, "ssoExternalIdDesc": { - "message": "SSO External ID is an unencrypted reference between Bitwarden and your configured SSO provider." + "message": "شناسه خارجی SSO یک مرجع بدون رمزگذاری بین Bitwarden و ارائه‌دهنده SSO تنظیم‌شده شما است." }, "nestCollectionUnder": { "message": "مجموعه لانه زیر" @@ -3387,10 +3381,10 @@ } }, "inviteSingleEmailDesc": { - "message": "You have 1 invite remaining." + "message": "شما یک دعوت‌نامه باقی‌مانده دارید." }, "inviteZeroEmailDesc": { - "message": "You have 0 invites remaining." + "message": "شما هیچ دعوت‌نامه باقی‌مانده‌ای ندارید." }, "userUsingTwoStep": { "message": "این کاربر از ورود دو مرحله ای برای محافظت از حساب خود استفاده می‌کند." @@ -3432,10 +3426,10 @@ "message": "همه" }, "addAccess": { - "message": "Add Access" + "message": "افزودن دسترسی" }, "addAccessFilter": { - "message": "Add Access Filter" + "message": "افزودن فیلتر دسترسی" }, "refresh": { "message": "تازه کردن" @@ -3504,10 +3498,10 @@ "message": "کد اشتباه" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "کد پین نادرست است" }, "pin": { - "message": "PIN", + "message": "پین", "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "exportedVault": { @@ -3556,7 +3550,7 @@ } }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "مشاهده همه گزینه‌های ورود به سیستم" }, "viewAllLoginOptions": { "message": "مشاهده همه گزینه‌های ورود به سیستم" @@ -3844,7 +3838,7 @@ } }, "unlinkedSso": { - "message": "Unlinked SSO." + "message": "SSO جدا شده است." }, "unlinkedSsoUser": { "message": "SSO برای کاربر $ID$ پیوند نخورده است.", @@ -3895,22 +3889,22 @@ "message": "دستگاه" }, "loginStatus": { - "message": "Login status" + "message": "وضیعت ورود" }, "firstLogin": { - "message": "First login" + "message": "اولین ورود" }, "trusted": { - "message": "Trusted" + "message": "مورد اعتماد" }, "needsApproval": { - "message": "Needs approval" + "message": "نیاز به تأیید دارد" }, "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "message": "آیا در تلاش برای ورود به سیستم هستید؟" }, "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "message": "تلاش برای ورود به سیستم توسط $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -3919,22 +3913,22 @@ } }, "deviceType": { - "message": "Device Type" + "message": "نوع دستگاه" }, "ipAddress": { - "message": "IP Address" + "message": "نشانی IP" }, "confirmLogIn": { - "message": "Confirm login" + "message": "تأیید ورود" }, "denyLogIn": { - "message": "Deny login" + "message": "رد ورود" }, "thisRequestIsNoLongerValid": { - "message": "This request is no longer valid." + "message": "این درخواست دیگر معتبر نیست." }, "logInConfirmedForEmailOnDevice": { - "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "message": "ورود به سیستم برای $EMAIL$ در $DEVICE$ تأیید شد", "placeholders": { "email": { "content": "$1", @@ -3947,16 +3941,16 @@ } }, "youDeniedALogInAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + "message": "شما تلاش برای ورود به سیستم از دستگاه دیگری را رد کردید. اگر واقعاً این شما بودید، سعی کنید دوباره با دستگاه وارد شوید." }, "loginRequestHasAlreadyExpired": { - "message": "Login request has already expired." + "message": "درخواست ورود قبلاً منقضی شده است." }, "justNow": { - "message": "Just now" + "message": "همین الان" }, "requestedXMinutesAgo": { - "message": "Requested $MINUTES$ minutes ago", + "message": "$MINUTES$ دقیقه قبل درخواست شد", "placeholders": { "minutes": { "content": "$1", @@ -3965,25 +3959,25 @@ } }, "creatingAccountOn": { - "message": "Creating account on" + "message": "در حال ساخت حساب کاربری روی" }, "checkYourEmail": { - "message": "Check your email" + "message": "ایمیل خود را چک کنید" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "دنبال کنید لینکی را که در ایمیل فرستاده شده به" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "و به ساختن حساب‌تان ادامه دهید." }, "noEmail": { - "message": "No email?" + "message": "ایمیلی دریافت نکردید؟" }, "goBack": { - "message": "Go back" + "message": "بازگشت به عقب" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "برای ویرایش آدرس ایمیل خود." }, "view": { "message": "مشاهده" @@ -4067,7 +4061,7 @@ "message": "ایمیل حساب تأیید شد" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "ایمیل تأیید شد" }, "emailVerifiedFailed": { "message": "تأیید ایمیل شما امکان پذیر نیست. سعی کنید یک ایمیل تأیید جدید ارسال کنید." @@ -4082,19 +4076,19 @@ "message": "به‌روزرسانی مرورگر" }, "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "message": "در حال تولید تحلیل‌های ریسک شما..." }, "updateBrowserDesc": { "message": "شما از یک مرورگر وب پشتیبانی نشده استفاده می‌کنید. گاوصندوق وب ممکن است به درستی کار نکند." }, "youHaveAPendingLoginRequest": { - "message": "You have a pending login request from another device." + "message": "شما یک درخواست ورود در انتظار از دستگاه دیگری دارید." }, "reviewLoginRequest": { - "message": "Review login request" + "message": "بررسی درخواست ورود" }, "freeTrialEndPromptCount": { - "message": "Your free trial ends in $COUNT$ days.", + "message": "دوره آزمایشی رایگان شما در $COUNT$ روز به پایان می‌رسد.", "placeholders": { "count": { "content": "$1", @@ -4103,7 +4097,7 @@ } }, "freeTrialEndPromptMultipleDays": { - "message": "$ORGANIZATION$, your free trial ends in $COUNT$ days.", + "message": "$ORGANIZATION$، دوره آزمایشی رایگان شما در $COUNT$ روز به پایان می‌رسد.", "placeholders": { "count": { "content": "$2", @@ -4116,7 +4110,7 @@ } }, "freeTrialEndPromptTomorrow": { - "message": "$ORGANIZATION$, your free trial ends tomorrow.", + "message": "$ORGANIZATION$، دوره آزمایشی رایگان شما فردا به پایان می‌رسد.", "placeholders": { "organization": { "content": "$1", @@ -4125,10 +4119,10 @@ } }, "freeTrialEndPromptTomorrowNoOrgName": { - "message": "Your free trial ends tomorrow." + "message": "دوره آزمایشی رایگان شما فردا به پایان می‌رسد." }, "freeTrialEndPromptToday": { - "message": "$ORGANIZATION$, your free trial ends today.", + "message": "$ORGANIZATION$، دوره آزمایشی رایگان شما امروز به پایان می‌رسد.", "placeholders": { "organization": { "content": "$1", @@ -4137,16 +4131,16 @@ } }, "freeTrialEndingTodayWithoutOrgName": { - "message": "Your free trial ends today." + "message": "دوره آزمایشی رایگان شما امروز به پایان می‌رسد." }, "clickHereToAddPaymentMethod": { - "message": "Click here to add a payment method." + "message": "برای افزودن روش پرداخت، اینجا کلیک کنید." }, "joinOrganization": { "message": "به سازمان بپیوندید" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "به $ORGANIZATIONNAME$ بپیوندید", "placeholders": { "organizationName": { "content": "$1", @@ -4158,7 +4152,7 @@ "message": "شما برای پیوستن به سازمان فهرست شده در بالا دعوت شده اید. برای پذیرش دعوت، باید وارد شوید یا یک حساب کاربری جدید در Bitwarden ایجاد کنید." }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "با تعیین یک کلمه عبور اصلی، عضویت خود در این سازمان را کامل کنید." }, "inviteAccepted": { "message": "دعوتنامه پذیرفته شد" @@ -4188,7 +4182,7 @@ "message": "اگر نمی‌توانید از طریق روش‌های ورود دو مرحله‌ای معمولی به حساب خود دسترسی پیدا کنید، می‌توانید از کد بازیابی ورود به سیستم دو مرحله‌ای خود برای خاموش کردن همه ارائه‌دهندگان دو مرحله‌ای در حساب خود استفاده کنید." }, "logInBelowUsingYourSingleUseRecoveryCode": { - "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account." + "message": "برای ورود، از کد بازیابی یک‌بار مصرف خود استفاده کنید. این کار تمام روش‌های ورود دو مرحله‌ای حساب کاربری شما را غیرفعال می‌کند." }, "recoverAccountTwoStep": { "message": "بازیابی حساب ورود دو مرحله ای" @@ -4209,7 +4203,7 @@ "message": "شما درخواست کرده اید که حساب Bitwarden خود را حذف کنید. برای تأیید از دکمه زیر استفاده کنید." }, "deleteRecoverOrgConfirmDesc": { - "message": "You have requested to delete your Bitwarden organization." + "message": "شما درخواست حذف سازمان Bitwarden خود را داده‌اید." }, "myOrganization": { "message": "سازمان من" @@ -4338,7 +4332,7 @@ "message": "برای اشتراک خود محدودیت جایگاه تعیین کنید. پس از رسیدن به این محدودیت، نمی‌توانید اعضای جدید را دعوت کنید." }, "limitSmSubscriptionDesc": { - "message": "برای اشتراک خود مدیر اسرار محدودیت جایگاه تعیین کنید. پس از رسیدن به این محدودیت، نمی‌توانید اعضای جدید را دعوت کنید." + "message": "برای اشتراک مدیر اسرار خود، محدودیت تعداد تعیین کنید. پس از رسیدن به این حد، قادر به دعوت اعضای جدید نخواهید بود." }, "maxSeatLimit": { "message": "محدودیت جایگاه (اختیاری)", @@ -4377,7 +4371,7 @@ "message": "اشتراک به‌روز شد" }, "subscribedToSecretsManager": { - "message": "Subscription updated. You now have access to Secrets Manager." + "message": "اشتراک به‌روزرسانی شد. اکنون به مدیر اسرار دسترسی دارید." }, "additionalOptions": { "message": "گزینه‌های اضافی" @@ -4389,10 +4383,10 @@ "message": "تنظیمات اشتراک شما منجر به تغییرات نسبتاً زیادی در مجموع صورتحساب شما می‌شود. اگر عضو‌هایی که به تازگی دعوت شده‌اند بیشتر از جایگاه‌های اشتراک شما باشند، بلافاصله هزینه‌ای متناسب برای عضو‌هایی اضافی دریافت خواهید کرد." }, "smStandaloneTrialSeatCountUpdateMessageFragment1": { - "message": "If you want to add additional" + "message": "اگر می‌خواهید موارد بیشتری اضافه کنید" }, "smStandaloneTrialSeatCountUpdateMessageFragment2": { - "message": "seats without the bundled offer, please contact" + "message": "برای صندلی‌های بدون پیشنهاد بسته‌ای، لطفا تماس بگیرید" }, "subscriptionUserSeatsLimitedAutoscale": { "message": "تنظیمات اشتراک شما منجر به تغییرات نسبتاً زیادی در مجموع صورتحساب شما می‌شود. اگر اعضای تازه دعوت شده از جایگاه‌های اشتراک شما بیشتر شوند، بلافاصله هزینه‌ای متناسب برای اعضای اضافی دریافت می‌کنید تا زمانی که به محدودیت جایگاه $MAX$ شما برسند.", @@ -4404,7 +4398,7 @@ } }, "subscriptionUserSeatsWithoutAdditionalSeatsOption": { - "message": "You can invite up to $COUNT$ members for no additional charge. Contact Customer Support to upgrade your plan and invite more members.", + "message": "شما می‌توانید تا سقف $COUNT$ عضو را بدون هزینه اضافی دعوت کنید. برای ارتقاء طرح خود و دعوت از اعضای بیشتر، با پشتیبانی مشتریان تماس بگیرید.", "placeholders": { "count": { "content": "$1", @@ -4422,7 +4416,7 @@ } }, "subscriptionUpgrade": { - "message": "You cannot invite more than $COUNT$ members without upgrading your plan.", + "message": "بدون ارتقای طرح خود نمی‌توانید بیش از $COUNT$ عضو دعوت کنید.", "placeholders": { "count": { "content": "$1", @@ -4449,7 +4443,7 @@ } }, "subscriptionSeatMaxReached": { - "message": "You cannot invite more than $COUNT$ members without increasing your subscription seats.", + "message": "شما نمی‌توانید بیش از $COUNT$ عضو دعوت کنید مگر اینکه تعداد صندلی‌های اشتراک خود را افزایش دهید.", "placeholders": { "count": { "content": "$1", @@ -4479,10 +4473,10 @@ } }, "encryptionKeyUpdateCannotProceed": { - "message": "Encryption key update cannot proceed" + "message": "به‌روزرسانی کلید رمزگذاری قابل انجام نیست" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "ویرایش $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4491,7 +4485,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "مرتب‌سازی مجدد $LABEL$. برای جابجایی مورد به بالا یا پایین از کلیدهای جهت‌نما استفاده کنید.", "placeholders": { "label": { "content": "$1", @@ -4500,7 +4494,7 @@ } }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ به بالا منتقل شد، موقعیت $INDEX$ از $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4517,7 +4511,7 @@ } }, "reorderFieldDown": { - "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ به پایین منتقل شد، موقعیت $INDEX$ از $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4534,7 +4528,7 @@ } }, "keyUpdateFoldersFailed": { - "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." + "message": "هنگام به‌روزرسانی کلید رمزگذاری، پوشه‌های شما قابل رمزگشایی نبودند. برای ادامه به‌روزرسانی، باید این پوشه‌ها حذف شوند. در صورت ادامه، هیچ‌یک از موارد موجود در گاوصندوق شما حذف نخواهد شد." }, "keyUpdated": { "message": "کلیدها به‌روز شد" @@ -4549,7 +4543,7 @@ "message": "پس از به‌روزرسانی کلید رمزگذاری، باید از سیستم خارج شوید و دوباره به همه برنامه‌های Bitwarden که در حال حاضر استفاده می‌کنید (مانند برنامه تلفن همراه یا برنامه‌های افزودنی مرورگر) وارد شوید. عدم خروج و ورود مجدد (که کلید رمزگذاری جدید شما را دانلود می‌کند) ممکن است منجر به خراب شدن داده‌ها شود. ما سعی خواهیم کرد شما را به طور خودکار از سیستم خارج کنیم، اما ممکن است با تأخیر انجام شود." }, "updateEncryptionKeyAccountExportWarning": { - "message": "Any account restricted exports you have saved will become invalid." + "message": "هرگونه برون ریزی حساب کاربری با دسترسی محدود که ذخیره کرده‌اید، نامعتبر خواهد شد." }, "subscription": { "message": "اشتراک" @@ -4579,19 +4573,19 @@ "message": "شما چیزی را انتخاب نکرده اید." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "پیشنهادها، اعلان‌ها و فرصت‌های تحقیقاتی Bitwarden را در صندوق ورودی خود دریافت کنید." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "لغو اشتراک" }, "atAnyTime": { - "message": "at any time." + "message": "در هر زمان." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "با ادامه دادن، شما موافقت می‌کنید که" }, "and": { - "message": "and" + "message": "و" }, "acceptPolicies": { "message": "با علامت زدن این کادر با موارد زیر موافقت می‌کنید:" @@ -4612,7 +4606,7 @@ "message": "متوقف شدن گاو‌صندوق" }, "vaultTimeout1": { - "message": "Timeout" + "message": "پایان زمان" }, "vaultTimeoutDesc": { "message": "انتخاب کنید که گاو‌صندوق شما چه زمانی عمل توقف زمانی گاوصندوق را انجام دهد." @@ -4681,7 +4675,7 @@ "message": "انتخاب شده" }, "recommended": { - "message": "Recommended" + "message": "توصیه می‌شود" }, "ownership": { "message": "مالکیت" @@ -4752,7 +4746,7 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "به‌محض تأیید درخواست، به شما اطلاع داده خواهد شد" }, "free": { "message": "رايگان", @@ -4805,7 +4799,7 @@ "message": "الزامات را برای قدرت کلمه عبور اصلی تنظیم کنید." }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "امتیاز قدرت کلمه عبور $SCORE$", "placeholders": { "score": { "content": "$1", @@ -4874,7 +4868,7 @@ "message": "حداقل تعداد کلمات" }, "overridePasswordTypePolicy": { - "message": "Password Type", + "message": "نوع کلمه عبور", "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { @@ -4991,10 +4985,10 @@ "message": "با استفاده از پورتال ورود واحد سازمان خود وارد شوید. لطفاً برای شروع، شناسه SSO سازمان خود را وارد کنید." }, "singleSignOnEnterOrgIdentifier": { - "message": "Enter your organization's SSO identifier to begin" + "message": "برای شروع، شناسه SSO سازمان خود را وارد کنید" }, "singleSignOnEnterOrgIdentifierText": { - "message": "To log in with your SSO provider, enter your organization's SSO identifier to begin. You may need to enter this SSO identifier when you log in from a new device." + "message": "برای ورود با استفاده از ارائه‌دهنده SSO خود، ابتدا شناسه SSO سازمانتان را وارد کنید. ممکن است هنگام ورود از یک دستگاه جدید نیز نیاز به وارد کردن این شناسه داشته باشید." }, "enterpriseSingleSignOn": { "message": "ورود به سیستم پروژه" @@ -5003,25 +4997,25 @@ "message": "اکنون می‌توانید این برگه را ببندید و در افزونه ادامه دهید." }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "شما با موفقیت وارد شدید" }, "thisWindowWillCloseIn5Seconds": { - "message": "This window will automatically close in 5 seconds" + "message": "این پنجره به‌صورت خودکار طی ۵ ثانیه بسته خواهد شد" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "می‌توانید این پنجره را ببندید" }, "includeAllTeamsFeatures": { "message": "همه ویژگی های تیم، به علاوه:" }, "includeAllTeamsStarterFeatures": { - "message": "All Teams Starter features, plus:" + "message": "تمام ویژگی‌های طرح ابتدایی تیم‌ها، به‌علاوه:" }, "chooseMonthlyOrAnnualBilling": { - "message": "Choose monthly or annual billing" + "message": "انتخاب صورتحساب ماهانه یا سالانه" }, "abilityToAddMoreThanNMembers": { - "message": "Ability to add more than $COUNT$ members", + "message": "امکان افزودن بیش از $COUNT$ عضو", "placeholders": { "count": { "content": "$1", @@ -5064,13 +5058,13 @@ "message": "اعضا را از پیوستن به سازمان‌های دیگر محدود کنید." }, "singleOrgPolicyDesc": { - "message": "Restrict members from joining other organizations. This policy is required for organizations that have enabled domain verification." + "message": "محدود کردن اعضا از پیوستن به سازمان‌های دیگر. این سیاست برای سازمان‌هایی که اعتبارسنجی دامنه را فعال کرده‌اند، الزامی است." }, "singleOrgBlockCreateMessage": { "message": "سازمان فعلی شما سیاستی دارد که به شما اجازه نمی‌دهد به بیش از یک سازمان بپیوندید. لطفاً با مدیران سازمان خود تماس بگیرید یا از یک حساب Bitwarden دیگر ثبت نام کنید." }, "singleOrgPolicyMemberWarning": { - "message": "Non-compliant members will be placed in revoked status until they leave all other organizations. Administrators are exempt and can restore members once compliance is met." + "message": "اعضای غیر مطابق در وضعیت لغو شده قرار می‌گیرند تا زمانی که از تمام سازمان‌های دیگر خارج شوند. مدیران مستثنی بوده و می‌توانند پس از برقراری انطباق، اعضا را بازیابی کنند." }, "requireSso": { "message": "نیاز به احراز هویت یکبار ورود به سیستم" @@ -5091,14 +5085,14 @@ "message": "مالکان و سرپرستان سازمان از اجرای این سیاست مستثنی هستند." }, "limitSendViews": { - "message": "Limit views" + "message": "محدود کردن نمایش‌ها" }, "limitSendViewsHint": { - "message": "No one can view this Send after the limit is reached.", + "message": "هیچ‌کس نمی‌تواند این را پس از رسیدن به محدودیت مشاهده کند.", "description": "Displayed under the limit views field on Send" }, "limitSendViewsCount": { - "message": "$ACCESSCOUNT$ views left", + "message": "$ACCESSCOUNT$ بازدید باقی مانده", "description": "Displayed under the limit views field on Send", "placeholders": { "accessCount": { @@ -5108,11 +5102,11 @@ } }, "sendDetails": { - "message": "Send details", + "message": "جزئیات ارسال", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeTextToShare": { - "message": "Text to share" + "message": "متن برای اشتراک گذاری" }, "sendTypeFile": { "message": "پرونده" @@ -5121,7 +5115,7 @@ "message": "متن" }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", + "message": "یک کلمه عبور اختیاری برای دریافت‌کنندگان اضافه کنید تا بتوانند به این ارسال دسترسی داشته باشند.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { @@ -5149,14 +5143,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendPermanentConfirmation": { - "message": "Are you sure you want to permanently delete this Send?", + "message": "مطمئن هستید می‌خواهید این ارسال را برای همیشه پاک کنید؟", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { "message": "تاریخ حذف" }, "deletionDateDescV2": { - "message": "The Send will be permanently deleted on this date.", + "message": "ارسال در این تاریخ به‌طور دائمی حذف خواهد شد.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -5180,7 +5174,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copyLink": { - "message": "Copy link" + "message": "کپی پیوند" }, "copySendLink": { "message": "پیوند ارسال را کپی کن", @@ -5206,7 +5200,7 @@ "message": "در انتظار حذف" }, "hideTextByDefault": { - "message": "Hide text by default" + "message": "متن را به‌صورت پیش‌فرض مخفی کن" }, "expired": { "message": "منقضی شده" @@ -5228,7 +5222,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "downloadAttachments": { - "message": "Download attachments" + "message": "بارگیری پیوست‌ها" }, "sendAccessUnavailable": { "message": "ارسالی که می‌خواهید به آن دسترسی پیدا کنید وجود ندارد یا دیگر در دسترس نیست.", @@ -5560,73 +5554,73 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see how it works, **or** try it now.'" }, "developmentDevOpsAndITTeamsChooseBWSecret": { - "message": "Development, DevOps, and IT teams choose Bitwarden Secrets Manager to securely manage and deploy their infrastructure and machine secrets." + "message": "تیم‌های توسعه، DevOps و فناوری اطلاعات مدیر اسرار Bitwarden را برای مدیریت و استقرار امن زیرساخت‌ها و اسرار ماشین خود انتخاب می‌کنند." }, "centralizeSecretsManagement": { - "message": "Centralize secrets management." + "message": "مدیریت متمرکز اسرار." }, "centralizeSecretsManagementDescription": { - "message": "Securely store and manage secrets in one location to prevent secret sprawl across your organization." + "message": "اسرار خود را به‌صورت ایمن در یک مکان ذخیره و مدیریت کنید تا از پراکندگی اسرار در سراسر سازمان جلوگیری شود." }, "preventSecretLeaks": { - "message": "Prevent secret leaks." + "message": "جلوگیری از نشت اسرار." }, "preventSecretLeaksDescription": { - "message": "Protect secrets with end-to-end encryption. No more hard coding secrets or sharing through .env files." + "message": "حفاظت از اسرار با رمزگذاری سرتاسری. دیگر نیازی به کدنویسی مستقیم اسرار یا به اشتراک‌گذاری آن‌ها از طریق فایل‌های .env نیست." }, "enhanceDeveloperProductivity": { - "message": "Enhance developer productivity." + "message": "افزایش بهره‌وری توسعه‌دهندگان." }, "enhanceDeveloperProductivityDescription": { - "message": "Programmatically retrieve and deploy secrets at runtime so developers can focus on what matters most, like improving code quality." + "message": "به‌صورت برنامه‌نویسی شده اسرار را در زمان اجرا دریافت و استقرار دهید تا توسعه‌دهندگان بتوانند روی موارد مهم‌تر مانند بهبود کیفیت کد تمرکز کنند." }, "strengthenBusinessSecurity": { - "message": "Strengthen business security." + "message": "تقویت امنیت کسب‌وکار." }, "strengthenBusinessSecurityDescription": { - "message": "Maintain tight control over machine and human access to secrets with SSO integrations, event logs, and access rotation." + "message": "کنترل دقیق بر دسترسی ماشین‌ها و افراد به اسرار را با ادغام SSO، گزارش رویدادها و چرخش دسترسی حفظ کنید." }, "tryItNow": { - "message": "Try it now" + "message": "امتحان کنید" }, "sendRequest": { - "message": "Send request" + "message": "ارسال درخواست" }, "addANote": { - "message": "Add a note" + "message": "افزودن يادداشت" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "مدیر اسرار Bitwarden" }, "moreProductsFromBitwarden": { - "message": "More products from Bitwarden" + "message": "محصولات بیشتر از Bitwarden" }, "requestAccessToSecretsManager": { - "message": "Request access to Secrets Manager" + "message": "درخواست دسترسی به مدیر اسرار" }, "youNeedApprovalFromYourAdminToTrySecretsManager": { - "message": "You need approval from your administrator to try Secrets Manager." + "message": "برای استفاده از مدیر اسرار، به تأیید مدیر خود نیاز دارید." }, "smAccessRequestEmailSent": { - "message": "Access request for secrets manager email sent to admins." + "message": "درخواست دسترسی به مدیر اسرار از طریق ایمیل برای مدیران ارسال شد." }, "requestAccessSMDefaultEmailContent": { - "message": "Hi,\n\nI am requesting a subscription to Bitwarden Secrets Manager for our team. Your support would mean a great deal!\n\nBitwarden Secrets Manager is an end-to-end encrypted secrets management solution for securely storing, sharing, and deploying machine credentials like API keys, database passwords, and authentication certificates.\n\nSecrets Manager will help us to:\n\n- Improve security\n- Streamline operations\n- Prevent costly secret leaks\n\nTo request a free trial for our team, please reach out to Bitwarden.\n\nThank you for your help!" + "message": "سلام،\n\nمن درخواست اشتراک مدیر اسرار Bitwarden را برای تیم‌مان دارم. حمایت شما واقعاً برای ما ارزشمند خواهد بود!\n\nمدیر اسرار Bitwarden یک راه حل مدیریت اسرار با رمزگذاری سرتاسری است که برای ذخیره‌سازی، به اشتراک‌گذاری و استقرار ایمن اطلاعات حساس ماشین مانند کلیدهای API، کلمات عبور پایگاه داده و گواهی‌های احراز هویت طراحی شده است.\n\nمدیر اسرار به ما کمک خواهد کرد تا:\n\n- امنیت را افزایش دهیم\n- عملیات را ساده‌سازی کنیم\n- از نشت‌های پرهزینه اسرار جلوگیری کنیم\n\nبرای درخواست یک دوره آزمایشی رایگان برای تیم‌مان، لطفاً با Bitwarden تماس بگیرید.\n\nاز همکاری شما سپاسگزارم!" }, "giveMembersAccess": { - "message": "Give members access:" + "message": "اعطا کردن دسترسی به اعضا:" }, "viewAndSelectTheMembers": { - "message": "view and select the members you want to give access to Secrets Manager." + "message": "مشاهده و انتخاب اعضایی که می‌خواهید به مدیر اسرار دسترسی داشته باشند." }, "openYourOrganizations": { - "message": "Open your organization's" + "message": "سازمان خود را باز کنید" }, "usingTheMenuSelect": { - "message": "Using the menu, select" + "message": "از منو، انتخاب کنید" }, "toGrantAccessToSelectedMembers": { - "message": "to grant access to selected members." + "message": "برای اعطای دسترسی به اعضای انتخاب شده." }, "sendVaultCardTryItNow": { "message": "الان امتحان کنید", @@ -5645,7 +5639,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about Bitwarden Send or sign up to **try it today.**'" }, "sendAccessCreatorIdentifier": { - "message": "Bitwarden member $USER_IDENTIFIER$ shared the following with you", + "message": "عضو Bitwarden $USER_IDENTIFIER$ موارد زیر را با شما به اشتراک گذاشت", "placeholders": { "user_identifier": { "content": "$1", @@ -5654,7 +5648,7 @@ } }, "viewSend": { - "message": "View Send", + "message": "مشاهده ارسال", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "viewSendHiddenEmailWarning": { @@ -5677,7 +5671,7 @@ "message": "هنگام ذخیره حذف و تاریخ انقضاء شما خطایی روی داد." }, "hideYourEmail": { - "message": "Hide your email address from viewers." + "message": "آدرس ایمیل خود را از بینندگان مخفی کنید." }, "webAuthnFallbackMsg": { "message": "برای تأیید 2FA خود لطفاً روی دکمه زیر کلیک کنید." @@ -5686,10 +5680,10 @@ "message": "تأیید اعتبار در WebAuthn" }, "readSecurityKey": { - "message": "Read security key" + "message": "خواندن کلید امنیتی" }, "awaitingSecurityKeyInteraction": { - "message": "Awaiting security key interaction..." + "message": "در انتظار تعامل با کلید امنیتی..." }, "webAuthnNotSupported": { "message": "WebAuthn در این مرورگر پشتیبانی نمی‌شود." @@ -5698,7 +5692,7 @@ "message": "WebAuthn با موفقیت تأیید شد! می‌توانید این برگه را ببندید." }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "کلمه عبور جدید شما نمی‌تواند با کلمه عبور فعلی‌تان یکسان باشد." }, "hintEqualsPassword": { "message": "اشاره به کلمه عبور شما نمی‌تواند همان کلمه عبور شما باشد." @@ -5887,32 +5881,32 @@ "message": "مستثنی شده، برای این اقدام قابل اجرا نیست" }, "nonCompliantMembersTitle": { - "message": "Non-compliant members" + "message": "اعضای غیر مطابق" }, "nonCompliantMembersError": { - "message": "Members that are non-compliant with the Single organization or Two-step login policy cannot be restored until they adhere to the policy requirements" + "message": "اعضایی که با سیاست تک سازمانی یا ورود دومرحله‌ای مطابقت ندارند، تا زمانی که الزامات این سیاست‌ها را رعایت نکنند، قابل بازیابی نیستند" }, "fingerprint": { "message": "اثر انگشت" }, "fingerprintPhrase": { - "message": "Fingerprint phrase:" + "message": "عبارت اثر انگشت:" }, "error": { "message": "خطا" }, "decryptionError": { - "message": "Decryption error" + "message": "خطای رمزگشایی" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden could not decrypt the vault item(s) listed below." + "message": "Bitwarden نتوانست مورد(های) گاوصندوق فهرست شده زیر را رمزگشایی کند." }, "contactCSToAvoidDataLossPart1": { - "message": "Contact customer success", + "message": "با بخش پشتیبانی مشتریان تماس بگیرید", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { - "message": "to avoid additional data loss.", + "message": "برای جلوگیری از، از دست دادن داده‌های بیشتر.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "accountRecoveryManageUsers": { @@ -5980,13 +5974,13 @@ "message": "یک سازمان مشتری جدید ایجاد کنید که با شما به عنوان ارائه دهنده مرتبط باشد. شما می توانید به این سازمان دسترسی داشته باشید و آن را مدیریت کنید." }, "newClient": { - "message": "New client" + "message": "مشتری جدید" }, "addExistingOrganization": { "message": "سازمان موجود را اضافه کنید" }, "addNewOrganization": { - "message": "Add new organization" + "message": "افزودن سازمان جدید" }, "myProvider": { "message": "ارائه دهنده‌ی من" @@ -6062,19 +6056,19 @@ "message": "کلمه عبور اصلی شما با یک یا چند سیاست سازمان‌تان مطابقت ندارد. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را به‌روز کنید. در صورت ادامه، شما از نشست فعلی خود خارج می‌شوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند." }, "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "message": "ورود خودکار کاربران به برنامه‌های مجاز" }, "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "message": "فرم‌های ورود به‌صورت خودکار پر شده و برای برنامه‌هایی که از ارائه‌دهنده هویت پیکربندی شده شما اجرا می‌شوند، ارسال خواهند شد." }, "automaticAppLoginIdpHostLabel": { - "message": "Identity provider host" + "message": "میزبان ارائه دهنده هویت" }, "automaticAppLoginIdpHostDesc": { - "message": "Enter your identity provider host URL. Enter multiple URLs by separating with a comma." + "message": "نشانی اینترنتی میزبان ارائه دهنده هویت خود را وارد کنید. برای وارد کردن چند نشانی، آن‌ها را با کاما از هم جدا کنید." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has updated your decryption options. Please set a master password to access your vault." + "message": "سازمان شما گزینه‌های رمزگشایی را به‌روزرسانی کرده است. لطفاً برای دسترسی به گاوصندوق خود، یک کلمه عبور اصلی تنظیم کنید." }, "maximumVaultTimeout": { "message": "متوقف شدن گاو‌صندوق" @@ -6108,7 +6102,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "حداکثر $HOURS$ ساعت و $MINUTES$ دقیقه.", "placeholders": { "hours": { "content": "$1", @@ -6264,10 +6258,10 @@ "message": "اعتبارسنجی گواهینامه‌ها" }, "spUniqueEntityId": { - "message": "Set a unique SP entity ID" + "message": "یک شناسه موجودیت SP منحصربه‌فرد تنظیم کنید" }, "spUniqueEntityIdDesc": { - "message": "Generate an identifier that is unique to your organization" + "message": "یک شناسه تولید کنید که منحصربه‌فرد برای سازمان شما باشد" }, "idpEntityId": { "message": "شناسه موجودیت" @@ -6303,19 +6297,19 @@ "message": "خانواده‌های Bitwarden رایگان" }, "sponsoredBitwardenFamilies": { - "message": "Sponsored families" + "message": "خانواده‌های تحت حمایت" }, "noSponsoredFamiliesMessage": { - "message": "No sponsored families" + "message": "خانواده‌های تحت حمایت وجود ندارند" }, "nosponsoredFamiliesDetails": { - "message": "Sponsored non-member families plans will display here" + "message": "طرح‌های خانواده‌های غیر عضو تحت حمایت در اینجا نمایش داده خواهند شد" }, "sponsorshipFreeBitwardenFamilies": { - "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + "message": "اعضای سازمان شما واجد شرایط استفاده از نسخه رایگان خانواده‌های Bitwarden هستند. شما می‌توانید نسخه رایگان خانواده‌های Bitwarden را برای کارمندانی که عضو سازمان Bitwarden شما نیستند، حمایت کنید. حمایت از یک فرد غیر عضو مستلزم وجود صندلی آزاد در سازمان شما است." }, "sponsoredFamiliesRemoveActiveSponsorship": { - "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + "message": "زمانی که حمایت فعالی را حذف می‌کنید، پس از تاریخ تمدید سازمان تحت حمایت، یک صندلی در سازمان شما آزاد خواهد شد." }, "sponsoredFamiliesEligible": { "message": "شما و خانواده‌تان واجد شرایط دریافت خانواده‌های Bitwarden رایگان هستید. با ایمیل شخصی خود بازخرید کنید تا اطلاعات خود را حتی زمانی که در محل کار نیستید ایمن نگه دارید." @@ -6324,28 +6318,28 @@ "message": "امروز برنامه رایگان Bitwarden برای خانواده‌های خود را بازخرید کنید تا اطلاعات خود را حتی زمانی که در محل کار نیستید ایمن نگه دارید." }, "sponsoredFamiliesIncludeMessage": { - "message": "The Bitwarden for Families plan includes" + "message": "طرح Bitwarden برای خانواده‌ها شامل" }, "sponsoredFamiliesPremiumAccess": { "message": "دسترسی پرمیوم برای حداکثر 6 کاربر" }, "sponsoredFamiliesSharedCollectionsForFamilyMembers": { - "message": "Shared collections for family members" + "message": "مجموعه‌های مشترک برای اعضای خانواده" }, "memberFamilies": { - "message": "Member families" + "message": "خانواده‌های اعضا" }, "noMemberFamilies": { - "message": "No member families" + "message": "خانواده‌های اعضا وجود ندارد" }, "noMemberFamiliesDescription": { - "message": "Members who have redeemed family plans will display here" + "message": "اعضایی که طرح‌های خانوادگی را فعال کرده‌اند، در اینجا نمایش داده خواهند شد" }, "membersWithSponsoredFamilies": { - "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + "message": "اعضای سازمان شما واجد شرایط استفاده از نسخه رایگان خانواده‌های Bitwarden هستند. در اینجا می‌توانید اعضایی را ببینید که سازمان خانوادگی را حمایت کرده‌اند." }, "organizationHasMemberMessage": { - "message": "A sponsorship cannot be sent to $EMAIL$ because they are a member of your organization.", + "message": "حمایت مالی نمی‌تواند به $EMAIL$ ارسال شود زیرا او عضو سازمان شما است.", "placeholders": { "email": { "content": "$1", @@ -6405,7 +6399,7 @@ "message": "حساب بازخرید شد" }, "revokeAccountMessage": { - "message": "Revoke account $NAME$", + "message": "حساب $NAME$ را لغو کنید", "placeholders": { "name": { "content": "$1", @@ -6471,16 +6465,16 @@ "message": "کد تأیید مورد نیاز است." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "احراز هویت لغو شد یا بیش از حد طول کشید. لطفاً دوباره تلاش کنید." }, "invalidVerificationCode": { "message": "کد تأیید نامعتبر است" }, "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + "message": "برای اعضای سازمان زیر، کلمه عبور اصلی دیگر لازم نیست. لطفاً دامنه زیر را با مدیر سازمان خود تأیید کنید." }, "keyConnectorDomain": { - "message": "Key Connector domain" + "message": "دامنه رابط کلید" }, "leaveOrganization": { "message": "ترک سازمان" @@ -6585,7 +6579,7 @@ "message": "توکن همگام‌سازی صورتحساب را مشاهده کنید" }, "generateBillingToken": { - "message": "Generate billing token" + "message": "تولید توکن پرداخت" }, "copyPasteBillingSync": { "message": "این توکن را کپی کرده و در تنظیمات همگام‌سازی صورتحساب سازمان خود میزبان جای‌گذاری کنید." @@ -6594,7 +6588,7 @@ "message": "توکن همگام‌سازی صورتحساب شما می‌تواند به تنظیمات اشتراک این سازمان دسترسی داشته باشد و آن را ویرایش کند." }, "manageBillingTokenSync": { - "message": "Manage Billing Token" + "message": "مدیریت توکن پرداخت" }, "setUpBillingSync": { "message": "همگام‌سازی صورتحساب را تنظیم کنید" @@ -6612,37 +6606,37 @@ "message": "چرخاندن توکن همگام‌سازی صورتحساب، توکن قبلی را باطل می‌کند." }, "selfHostedServer": { - "message": "self-hosted" + "message": "خود میزبان" }, "customEnvironment": { - "message": "Custom environment" + "message": "محیط سفارشی" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "نشانی اینترنتی پایه نصب Bitwarden خود را که به‌صورت داخلی میزبانی شده مشخص کنید.\nمثال: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "برای پیکربندی پیشرفته، می‌توانید نشانی اینترنتی پایه هر سرویس را به‌صورت مستقل مشخص کنید." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "شما باید یا نشانی اینترنتی پایه سرور را اضافه کنید، یا حداقل یک محیط سفارشی تعریف کنید." }, "apiUrl": { - "message": "API server URL" + "message": "نشانی API سرور" }, "webVaultUrl": { - "message": "Web vault server URL" + "message": "نشانی اینترنتی سرور گاوصندوق وب" }, "identityUrl": { - "message": "Identity server URL" + "message": "نشانی سرور شناسایی" }, "notificationsUrl": { - "message": "Notifications server URL" + "message": "نشانی سرور اعلان‌ها" }, "iconsUrl": { - "message": "Icons server URL" + "message": "نشانی سرور آیکون ها" }, "environmentSaved": { - "message": "Environment URLs saved" + "message": "نشانی‌های اینترنتی محیط ذخیره شد" }, "selfHostingTitle": { "message": "خود میزبانی" @@ -6660,7 +6654,7 @@ "message": "توکن همگام‌سازی صورتحساب" }, "automaticBillingSyncDesc": { - "message": "Automatic sync unlocks Families sponsorships and allows you to sync your license without uploading a file. After making updates in the Bitwarden cloud server, select Sync License to apply changes." + "message": "همگام‌سازی خودکار، حمایت‌های خانواده‌ها را فعال می‌کند و به شما امکان می‌دهد بدون بارگذاری پرونده، مجوز خود را همگام‌سازی کنید. پس از انجام تغییرات در سرور ابری Bitwarden، گزینه «همگام‌سازی مجوز» را انتخاب کنید تا تغییرات اعمال شود." }, "active": { "message": "فعال" @@ -6730,7 +6724,7 @@ "message": "اگر شناسه موجودیت یک نشانی اینترنتی نباشد، الزامی است." }, "offerNoLongerValid": { - "message": "This offer is no longer valid. Contact your organization administrators for more information." + "message": "این پیشنهاد دیگر معتبر نیست. برای کسب اطلاعات بیشتر با مدیران سازمان خود تماس بگیرید." }, "openIdOptionalCustomizations": { "message": "سفارشی سازی اختیاری" @@ -6760,7 +6754,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included", + "message": "فقط موردهای فردی گاوصندوق شامل پیوست‌ها که به $EMAIL$ مرتبط هستند برون ریزی خواهند شد. موردهای گاوصندوق سازمانی شامل نمی‌شوند", "placeholders": { "email": { "content": "$1", @@ -6822,10 +6816,28 @@ "message": "ایجاد نام کاربری" }, "generateEmail": { - "message": "Generate email" + "message": "تولید ایمیل" + }, + "generatePassword": { + "message": "تولید کلمه عبور" + }, + "generatePassphrase": { + "message": "تولید عبارت عبور" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "مقدار باید بین $MIN$ و $MAX$ باشد.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -6839,7 +6851,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " برای تولید یک کلمه عبور قوی، از $RECOMMENDED$ کاراکتر یا بیشتر استفاده کنید.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6849,7 +6861,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " برای تولید یک عبارت عبور قوی، از $RECOMMENDED$ واژه یا بیشتر استفاده کنید.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -6872,7 +6884,7 @@ "message": "از صندوق ورودی پیکربندی شده دامنه خود استفاده کنید." }, "useThisEmail": { - "message": "Use this email" + "message": "از این ایمیل استفاده شود" }, "random": { "message": "تصادفی", @@ -6882,23 +6894,26 @@ "message": "کلمه تصادفی" }, "usernameGenerator": { - "message": "Username generator" + "message": "تولید کننده نام کاربری" }, "useThisPassword": { - "message": "Use this password" + "message": "از این کلمه عبور استفاده کن" + }, + "useThisPassphrase": { + "message": "Use this passphrase" }, "useThisUsername": { - "message": "Use this username" + "message": "از این نام کاربری استفاده کن" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "کلمه عبور ایمن ساخته شد! فراموش نکنید کلمه عبور خود را در وب‌سایت نیز به‌روزرسانی کنید." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "از تولید کننده استفاده کنید", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "برای ایجاد یک کلمه عبور قوی و منحصر به فرد", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "service": { @@ -6964,15 +6979,15 @@ "message": "یک نام مستعار ایمیل با یک سرویس ارسال خارجی ایجاد کنید." }, "forwarderDomainName": { - "message": "Email domain", + "message": "دامنه ایمیل", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "دامنه‌ای را انتخاب کنید که توسط سرویس انتخاب شده پشتیبانی می‌شود", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ خطا: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -6986,11 +7001,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "تولید شده توسط Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "وب‌سایت: $WEBSITE$. تولید شده توسط Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -7000,7 +7015,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "توکن API نامعتبر برای $SERVICENAME$", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -7010,7 +7025,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "توکن API نامعتبر برای $SERVICENAME$: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -7024,7 +7039,7 @@ } }, "forwaderInvalidOperation": { - "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "message": "$SERVICENAME$ درخواست شما را رد کرد. لطفاً برای دریافت کمک با ارائه‌دهنده سرویس خود تماس بگیرید.", "description": "Displayed when the user is forbidden from using the API by the forwarding service.", "placeholders": { "servicename": { @@ -7034,7 +7049,7 @@ } }, "forwaderInvalidOperationWithMessage": { - "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "message": "$SERVICENAME$ درخواست شما را رد کرد: $ERRORMESSAGE$", "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -7048,7 +7063,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "دریافت شناسه حساب ایمیل ماسک شده از $SERVICENAME$ امکان‌پذیر نیست.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -7058,7 +7073,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "دامنه $SERVICENAME$ نامعتبر.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -7068,7 +7083,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "نشانی $SERVICENAME$ نامعتبر.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -7078,7 +7093,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "خطای $SERVICENAME$ نامعلومی رخ داد.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -7088,7 +7103,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "فرواردکننده ناشناخته: $SERVICENAME$.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -7134,7 +7149,7 @@ "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimIntegrationDescription": { - "message": "Automatically provision users and groups with your preferred identity provider via SCIM provisioning. Find supported integrations", + "message": "کاربران و گروه‌ها را به‌طور خودکار از طریق ارائه‌دهنده هویت مورد نظر خود با استفاده از تأمین‌کننده SCIM ایجاد کنید. ادغام‌های پشتیبانی‌شده را بیابید", "description": "the text, 'SCIM', is an acronym and should not be translated." }, "scimEnabledCheckboxDesc": { @@ -7256,10 +7271,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "۱ فیلد به توجه شما نیاز دارد." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "فیلدهای $COUNT$ به توجه شما نیاز دارند.", "placeholders": { "count": { "content": "$1", @@ -7268,22 +7283,22 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "خطا در اتصال به سرویس Duo. از روش ورود دو مرحله‌ای دیگری استفاده کنید یا برای دریافت کمک با Duo تماس بگیرید." }, "duoRequiredByOrgForAccount": { - "message": "Duo two-step login is required for your account." + "message": "ورود دو مرحله ای Duo برای حساب کاربری شما لازم است." }, "duoTwoFactorRequiredPageSubtitle": { - "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + "message": "برای حساب کاربری شما ورود دو مرحله‌ای Duo لازم است. مراحل زیر را دنبال کنید تا ورود خود را کامل کنید." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Follow the steps below to finish logging in." + "message": "مراحل زیر را دنبال کنید تا وارد سیستم شوید." }, "followTheStepsBelowToFinishLoggingInWithSecurityKey": { - "message": "Follow the steps below to finish logging in with your security key." + "message": "مراحل زیر را برای کامل کردن ورود با کلید امنیتی خود دنبال کنید." }, "launchDuo": { - "message": "Launch Duo" + "message": "اجرای Duo" }, "turnOn": { "message": "روشن" @@ -7792,7 +7807,7 @@ "message": "با افزودن مجموعه‌ها به این گروه، اجازه دسترسی به مجموعه‌ها را بدهید." }, "restrictedCollectionAssignmentDesc": { - "message": "You can only assign collections you manage." + "message": "شما فقط می‌توانید مجموعه‌هایی را اختصاص دهید که مدیریت آن‌ها بر عهده شماست." }, "selectMembers": { "message": "انتخاب اعضا" @@ -7903,43 +7918,43 @@ } }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "برای این اقدام تأیید لازم است. یک کد پین تعیین کنید تا ادامه دهید." }, "setPin": { - "message": "Set PIN" + "message": "تنظیم کد پین" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "تأیید با استفاده از بیومتریک" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "در انتظار تأیید" }, "couldNotCompleteBiometrics": { - "message": "Could not complete biometrics." + "message": "تکمیل بیومتریک ممکن نشد." }, "needADifferentMethod": { - "message": "Need a different method?" + "message": "نیازمند روش دیگری هستید؟" }, "useMasterPassword": { - "message": "Use master password" + "message": "استفاده از کلمه عبور اصلی" }, "usePin": { - "message": "Use PIN" + "message": "استفاده از کد پین" }, "useBiometrics": { - "message": "Use biometrics" + "message": "استفاده از بیومتریک" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "کد تأییدی را که به ایمیل شما ارسال شده است وارد کنید." }, "resendCode": { - "message": "Resend code" + "message": "ارسال دوباره کد" }, "memberColumnHeader": { - "message": "Member" + "message": "عضو" }, "groupSlashMemberColumnHeader": { - "message": "Group/Member" + "message": "گروه/عضو" }, "selectGroupsAndMembers": { "message": "گروه‌ها و اعضا را انتخاب کنید" @@ -7948,7 +7963,7 @@ "message": "انتخاب گروه‌ها" }, "userPermissionOverrideHelperDesc": { - "message": "Permissions set for a member will replace permissions set by that member's group." + "message": "مجوزهای تنظیم شده برای یک عضو، جایگزین مجوزهای تعیین شده توسط گروه آن عضو می‌شود." }, "noMembersOrGroupsAdded": { "message": "عضو یا گروهی اضافه نشد" @@ -7963,7 +7978,7 @@ "message": "دعوت از عضو" }, "addSponsorship": { - "message": "Add sponsorship" + "message": "افزودن حمایت مالی" }, "needsConfirmation": { "message": "نیاز به تأیید دارد" @@ -7996,7 +8011,7 @@ } }, "teamsStarterPlanInvLimitReachedManageBilling": { - "message": "Teams Starter plans may have up to $SEATCOUNT$ members. Upgrade to your plan to invite more members.", + "message": "طرح‌های شروع تیم‌ها ممکن است تا $SEATCOUNT$ عضو داشته باشند. برای دعوت اعضای بیشتر، طرح‌ خود را ارتقا دهید.", "placeholders": { "seatcount": { "content": "$1", @@ -8005,7 +8020,7 @@ } }, "teamsStarterPlanInvLimitReachedNoManageBilling": { - "message": "Teams Starter plans may have up to $SEATCOUNT$ members. Contact your organization owner to upgrade your plan and invite more members.", + "message": "طرح‌های شروع تیم‌ها ممکن است تا $SEATCOUNT$ عضو داشته باشند. برای ارتقا طرح و دعوت اعضای بیشتر با مالک سازمان خود تماس بگیرید.", "placeholders": { "seatcount": { "content": "$1", @@ -8053,7 +8068,7 @@ "message": "بارگذاری فایل" }, "upload": { - "message": "Upload" + "message": "بارگذاری" }, "acceptedFormats": { "message": "فرمت های پذیرفته شده:" @@ -8065,13 +8080,13 @@ "message": "یا" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "با استفاده از بیومتریک باز کنید" }, "unlockWithPin": { - "message": "Unlock with PIN" + "message": "باز کردن با کد پین" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "باز کردن قفل با کلمه عبور اصلی" }, "licenseAndBillingManagement": { "message": "مدیریت مجوز و صورتحساب" @@ -8083,7 +8098,7 @@ "message": "آپلود دستی" }, "manualBillingTokenUploadDesc": { - "message": "If you do not want to opt into billing sync, manually upload your license here. This will not automatically unlock Families sponsorships." + "message": "اگر نمی‌خواهید همگام‌سازی صورتحساب را فعال کنید، می‌توانید مجوز خود را به‌صورت دستی در اینجا بارگذاری کنید. این کار به‌صورت خودکار حمایت‌های خانواده را فعال نخواهد کرد." }, "syncLicense": { "message": "مجوز همگام‌سازی" @@ -8155,7 +8170,7 @@ "message": "تنظیمات رمزگذاری خود را برای رعایت توصیه‌های امنیتی جدید و بهبود حفاظت از حساب به‌روزرسانی کنید." }, "kdfSettingsChangeLogoutWarning": { - "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login, if any. We recommend exporting your vault before changing your encryption settings to prevent data loss." + "message": "ادامه دادن باعث خروج شما از تمام نشست‌های فعال خواهد شد. برای ادامه باید دوباره وارد شوید و در صورت فعال بودن، ورود دو مرحله‌ای را کامل کنید. توصیه می‌کنیم قبل از تغییر تنظیمات رمزنگاری، از گاوصندوق خود خروجی بگیرید تا از دست رفتن داده‌ها جلوگیری شود." }, "secretsManager": { "message": "مدیر رازها" @@ -8208,7 +8223,7 @@ "description": "Software Development Kit" }, "createAnAccount": { - "message": "Create an account" + "message": "ایجاد حساب کاربری" }, "createSecret": { "message": "ساختن یک راز" @@ -8352,16 +8367,16 @@ "message": "ورود به سیستم آغاز شد" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "این دستگاه را به خاطر بسپار تا ورودهای بعدی بدون مشکل انجام شود" }, "deviceApprovalRequired": { "message": "تأیید دستگاه لازم است. یک روش تأیید انتخاب کنید:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "تأیید دستگاه لازم است" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "یکی از گزینه‌های تأیید زیر را انتخاب کنید" }, "rememberThisDevice": { "message": "این دستگاه را به خاطر بسپار" @@ -8382,43 +8397,43 @@ "message": "دستگاه‌های مورد اعتماد" }, "memberDecryptionOptionTdeDescPart1": { - "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", + "message": "اعضا هنگام ورود با SSO نیازی به وارد کردن کلمه عبور اصلی نخواهند داشت. کلمه عبور اصلی با یک کلید رمزنگاری ذخیره شده در دستگاه جایگزین می‌شود که باعث می‌شود آن دستگاه مورد اعتماد باشد. اولین دستگاهی که عضو با آن حساب کاربری خود را ایجاد کرده و وارد می‌شود، به عنوان دستگاه مورد اعتماد شناخته می‌شود. دستگاه‌های جدید باید توسط یک دستگاه مورد اعتماد موجود یا توسط مدیر تایید شوند. ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescLink1": { - "message": "single organization", + "message": "سازمان واحد", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescPart2": { - "message": "policy,", + "message": "سیاست‌ها", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescLink2": { - "message": "SSO required", + "message": "SSO الزامی است", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescPart3": { - "message": "policy, and", + "message": "سیاست‌ها و", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescLink3": { - "message": "account recovery administration", + "message": "مدیریت بازیابی حساب کاربری", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescPart4": { - "message": "policy will turn on when this option is used.", + "message": "این سیاست زمانی فعال می‌شود که این گزینه استفاده شود.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "Your organization permissions were updated, requiring you to set a master password.", + "message": "مجوزهای سازمان شما به‌روزرسانی شد، باید یک کلمه عبور اصلی تنظیم کنید.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Your organization requires you to set a master password.", + "message": "سازمانتان از شما می‌خواهد که یک کلمه عبور اصلی تنظیم کنید.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "از میان $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -8436,7 +8451,7 @@ } }, "verificationRequired": { - "message": "Verification required", + "message": "تأیید لازم است", "description": "Default title for the user verification dialog." }, "recoverAccount": { @@ -8485,16 +8500,16 @@ "message": "درخواست تأیید" }, "deviceApproved": { - "message": "Device approved" + "message": "دستگاه تایید شد" }, "deviceRemoved": { - "message": "Device removed" + "message": "دستگاه حذف شد" }, "removeDevice": { - "message": "Remove device" + "message": "حذف دستگاه" }, "removeDeviceConfirmation": { - "message": "Are you sure you want to remove this device?" + "message": "آیا مطمئن هستید که می‌خواهید این دستگاه را حذف کنید؟" }, "noDeviceRequests": { "message": "بدون درخواست دستگاه" @@ -8551,7 +8566,7 @@ "message": "تأیید دستگاه درخواست." }, "tdeOffboardingPasswordSet": { - "message": "User set a master password during TDE offboarding." + "message": "کاربر در هنگام خروج از TDE، یک کلمه عبور اصلی تنظیم کرده است." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "نسخه آزمایشی رایگان ۷ روزه Bitwarden را با $ORG$ شروع کنید", @@ -8563,7 +8578,7 @@ } }, "startYour7DayFreeTrialOfBitwardenSecretsManagerFor": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for $ORG$", + "message": "شروع دوره آزمایشی رایگان ۷ روزه مدیر اسرار Bitwarden برای $ORG$", "placeholders": { "org": { "content": "$1", @@ -8575,7 +8590,7 @@ "message": "بعدی" }, "ssoLoginIsRequired": { - "message": "SSO login is required" + "message": "ورود از طریق SSO الزامی است" }, "selectedRegionFlag": { "message": "پرچم منطقه انتخاب شد" @@ -8599,7 +8614,7 @@ "message": "ایمیل کاربر وجود ندارد" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "ایمیل کاربر فعال پیدا نشد. در حال خارج کردن شما از سیستم هستیم." }, "deviceTrusted": { "message": "دستگاه مورد اعتماد است" @@ -8691,25 +8706,25 @@ } }, "collectionManagement": { - "message": "Collection management" + "message": "مدیریت مجموعه" }, "collectionManagementDesc": { - "message": "Manage the collection behavior for the organization" + "message": "مدیریت رفتار مجموعه برای سازمان" }, "limitCollectionCreationDesc": { - "message": "Limit collection creation to owners and admins" + "message": "محدود کردن ایجاد مجموعه فقط به مالکان و مدیران" }, "limitCollectionDeletionDesc": { - "message": "Limit collection deletion to owners and admins" + "message": "محدود کردن حذف مجموعه فقط به مالکان و مدیران" }, "limitItemDeletionDescription": { - "message": "Limit item deletion to members with the Manage collection permissions" + "message": "حذف موارد را فقط به اعضایی که دارای مجوز مدیریت مجموعه هستند محدود کنید" }, "allowAdminAccessToAllCollectionItemsDesc": { - "message": "Owners and admins can manage all collections and items" + "message": "مالکان و مدیران می‌توانند تمام مجموعه‌ها و موارد را مدیریت کنند" }, "updatedCollectionManagement": { - "message": "Updated collection management setting" + "message": "تنظیم مدیریت مجموعه به‌روزرسانی شد" }, "passwordManagerPlanPrice": { "message": "قیمت طرح مدیریت کلمه عبور" @@ -8742,29 +8757,29 @@ "message": "آزمایشی" }, "assignCollectionAccess": { - "message": "Assign collection access" + "message": "اختصاص دسترسی به مجموعه" }, "editedCollections": { - "message": "Edited collections" + "message": "مجموعه‌های ویرایش شده" }, "baseUrl": { "message": "نشانی اینترنتی سرور" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "نشانی اینترنتی سرور خود میزبان", "description": "Label for field requesting a self-hosted integration service URL" }, "alreadyHaveAccount": { "message": "پیشتر حساب کاربری داشته اید؟" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "تغییر وضعیت ناوبری کناری" }, "skipToContent": { - "message": "Skip to content" + "message": "پرش به محتوا" }, "managePermissionRequired": { - "message": "At least one member or group must have can manage permission." + "message": "حداقل یک عضو یا گروه باید دارای مجوز مدیریت کردن باشد." }, "typePasskey": { "message": "کلید عبور" @@ -8776,7 +8791,7 @@ "message": "کلید عبور در مورد شبیه سازی شده کپی نمی‌شود. آیا می‌خواهید به شبیه سازی این مورد ادامه دهید؟" }, "modifiedCollectionManagement": { - "message": "Modified collection management setting $ID$.", + "message": "تنظیم مدیریت مجموعه با شناسه $ID$ تغییر یافت.", "placeholders": { "id": { "content": "$1", @@ -8785,60 +8800,60 @@ } }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "دستورالعمل‌های کامل را در سایت راهنمای ما مشاهده کنید در", "description": "This is followed a by a hyperlink to the help website." }, "installBrowserExtension": { - "message": "Install browser extension" + "message": "نصب افزونه مرورگر" }, "installBrowserExtensionDetails": { - "message": "Use the extension to quickly save logins and auto-fill forms without opening the web app." + "message": "برای ذخیره سریع ورودها و پر کردن خودکار فرم‌ها بدون باز کردن برنامه وب، از افزونه استفاده کنید." }, "projectAccessUpdated": { - "message": "Project access updated" + "message": "دسترسی پروژه به‌روزرسانی شد" }, "unexpectedErrorSend": { - "message": "An unexpected error has occurred while loading this Send. Try again later." + "message": "هنگام بارگذاری این ارسال، خطای غیرمنتظره‌ای رخ داده است. لطفاً بعداً دوباره تلاش کنید." }, "seatLimitReached": { - "message": "Seat limit has been reached" + "message": "حداکثر ظرفیت صندلی‌ها پر شده است" }, "contactYourProvider": { - "message": "Contact your provider to purchase additional seats." + "message": "برای خرید صندلی‌های بیشتر با ارائه‌دهنده خود تماس بگیرید." }, "seatLimitReachedContactYourProvider": { - "message": "Seat limit has been reached. Contact your provider to purchase additional seats." + "message": "حداکثر ظرفیت صندلی‌ها پر شده است. برای خرید صندلی‌های بیشتر با ارائه‌دهنده خود تماس بگیرید." }, "collectionAccessRestricted": { - "message": "Collection access is restricted" + "message": "دسترسی به مجموعه محدود شده است" }, "readOnlyCollectionAccess": { - "message": "You do not have access to manage this collection." + "message": "شما دسترسی مدیریت این مجموعه را ندارید." }, "grantManageCollectionWarningTitle": { - "message": "Missing Manage Collection Permissions" + "message": "مجوزهای مدیریت مجموعه وجود ندارد" }, "grantManageCollectionWarning": { - "message": "Grant Manage collection permissions to allow full collection management including deletion of collection." + "message": "برای اجازه مدیریت کامل مجموعه، از جمله حذف مجموعه، مجوزهای مدیریت مجموعه را اعطا کنید." }, "grantCollectionAccess": { - "message": "Grant groups or members access to this collection." + "message": "دسترسی گروه‌ها یا اعضا را به این مجموعه اعطا کنید." }, "grantCollectionAccessMembersOnly": { - "message": "Grant members access to this collection." + "message": "به اعضا دسترسی به این مجموعه بدهید." }, "adminCollectionAccess": { - "message": "Administrators can access and manage collections." + "message": "مدیران می‌توانند به مجموعه‌ها دسترسی داشته و آن‌ها را مدیریت کنند." }, "serviceAccountAccessUpdated": { - "message": "Service account access updated" + "message": "دسترسی حساب کاربری سرویس به‌روزرسانی شد" }, "commonImportFormats": { - "message": "Common formats", + "message": "فرمت‌های رایج", "description": "Label indicating the most common import formats" }, "maintainYourSubscription": { - "message": "To maintain your subscription for $ORG$, ", + "message": "برای حفظ اشتراک خود در $ORG$، ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method.'", "placeholders": { "org": { @@ -8848,103 +8863,103 @@ } }, "addAPaymentMethod": { - "message": "add a payment method", + "message": "یک روش پرداخت اضافه کنید", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To maintain your subscription for $ORG$, add a payment method'" }, "organizationInformation": { - "message": "Organization information" + "message": "اطلاعات سازمان" }, "confirmationDetails": { - "message": "Confirmation details" + "message": "تأیید جزئیات" }, "smFreeTrialThankYou": { - "message": "Thank you for signing up for Bitwarden Secrets Manager!" + "message": "متشکریم که در مدیر اسرار Bitwarden ثبت‌نام کردید!" }, "smFreeTrialConfirmationEmail": { - "message": "We've sent a confirmation email to your email at " + "message": "ما یک ایمیل تأیید به نشانی ایمیل شما ارسال کرده‌ایم در " }, "sorryToSeeYouGo": { - "message": "Sorry to see you go! Help improve Bitwarden by sharing why you're canceling.", + "message": "متأسفیم که می‌روید! با به اشتراک‌گذاری دلیل لغو اشتراک، به بهبود Bitwarden کمک کنید.", "description": "A message shown to users as part of an offboarding survey asking them to provide more information on their subscription cancelation." }, "selectCancellationReason": { - "message": "Select a reason for canceling", + "message": "لطفاً دلیل لغو اشتراک را انتخاب کنید", "description": "Used as a form field label for a select input on the offboarding survey." }, "anyOtherFeedback": { - "message": "Is there any other feedback you'd like to share?", + "message": "آیا بازخورد دیگری هست که مایل باشید با ما در میان بگذارید؟", "description": "Used as a form field label for a textarea input on the offboarding survey." }, "missingFeatures": { - "message": "Missing features", + "message": "ویژگی‌ها وجود ندارد", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "movingToAnotherTool": { - "message": "Moving to another tool", + "message": "در حال انتقال به ابزار دیگری هستم", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "tooDifficultToUse": { - "message": "Too difficult to use", + "message": "استفاده از آن خیلی دشوار است", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "notUsingEnough": { - "message": "Not using enough", + "message": "به اندازه کافی از آن استفاده نمی‌کنم", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "tooExpensive": { - "message": "Too expensive", + "message": "خیلی گران است", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, "freeForOneYear": { - "message": "Free for 1 year" + "message": "رایگان برای یک سال" }, "newWebApp": { - "message": "Welcome to the new and improved web app. Learn more about what’s changed." + "message": "به برنامه وب جدید و بهبود یافته خوش آمدید. برای آشنایی با تغییرات انجام شده بیشتر بدانید." }, "releaseBlog": { - "message": "Read release blog" + "message": "مطالعه‌ی بلاگ انتشار نسخه" }, "adminConsole": { - "message": "Admin Console" + "message": "کنسول مدیر" }, "providerPortal": { - "message": "Provider Portal" + "message": "درگاه ارائه‌دهنده" }, "success": { - "message": "Success" + "message": "موفقیت آمیز بود" }, "restrictedGroupAccess": { - "message": "You cannot add yourself to groups." + "message": "شما نمی‌توانید خودتان را به گروه‌ها اضافه کنید." }, "cannotAddYourselfToCollections": { - "message": "You cannot add yourself to collections." + "message": "شما نمی‌توانید خودتان را به مجموعه‌ها اضافه کنید." }, "assign": { - "message": "Assign" + "message": "اختصاص بدهید" }, "assignToCollections": { - "message": "Assign to collections" + "message": "اختصاص به مجموعه‌ها" }, "assignToTheseCollections": { - "message": "Assign to these collections" + "message": "اختصاص به این مجموعه‌ها" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Only organization members with access to these collections will be able to see the item." + "message": "فقط اعضای سازمانی که به این مجموعه‌ها دسترسی دارند قادر به مشاهده این مورد خواهند بود." }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "فقط اعضای سازمانی که به این مجموعه‌ها دسترسی دارند قادر به مشاهده این موارد خواهند بود." }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "مجموعه‌ها را برای اختصاص انتخاب کنید" }, "noCollectionsAssigned": { - "message": "No collections have been assigned" + "message": "هیچ مجموعه‌ای اختصاص داده نشده است" }, "successfullyAssignedCollections": { - "message": "Successfully assigned collections" + "message": "مجموعه‌ها با موفقیت اختصاص داده شدند" }, "bulkCollectionAssignmentWarning": { - "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "message": "شما $TOTAL_COUNT$ مورد را انتخاب کرده‌اید. نمی‌توانید $READONLY_COUNT$ مورد را به‌روزرسانی کنید زیرا دسترسی ویرایش ندارید.", "placeholders": { "total_count": { "content": "$1", @@ -8957,61 +8972,61 @@ } }, "addField": { - "message": "Add field" + "message": "افزودن فیلد" }, "editField": { - "message": "Edit field" + "message": "ویرایش فیلد" }, "items": { - "message": "Items" + "message": "موارد" }, "assignedSeats": { - "message": "Assigned seats" + "message": "صندلی‌های اختصاص داده شده" }, "assigned": { - "message": "Assigned" + "message": "اختصاص یافته" }, "used": { - "message": "Used" + "message": "استفاده شده" }, "remaining": { - "message": "Remaining" + "message": "باقی مانده" }, "unlinkOrganization": { - "message": "Unlink organization" + "message": "قطع ارتباط با سازمان" }, "manageSeats": { - "message": "MANAGE SEATS" + "message": "مدیریت صندلی‌ها" }, "manageSeatsDescription": { - "message": "Adjustments to seats will be reflected in the next billing cycle." + "message": "تغییرات در تعداد صندلی‌ها در دوره صورتحساب بعدی اعمال خواهد شد." }, "unassignedSeatsDescription": { - "message": "Unassigned seats" + "message": "صندلی‌های تخصیص داده نشده" }, "purchaseSeatDescription": { - "message": "Additional seats purchased" + "message": "صندلی‌های اضافی خریداری شده" }, "assignedSeatCannotUpdate": { - "message": "Assigned Seats can not be updated. Please contact your organization owner for assistance." + "message": "صندلی‌های اختصاص داده شده قابل به‌روزرسانی نیستند. لطفاً برای دریافت کمک با مدیر سازمان خود تماس بگیرید." }, "subscriptionUpdateFailed": { - "message": "Subscription update failed" + "message": "به‌روزرسانی اشتراک ناموفق بود" }, "trial": { - "message": "Trial", + "message": "نسخه آزمایشی", "description": "A subscription status label." }, "pastDue": { - "message": "Past due", + "message": "بدهی معوق", "description": "A subscription status label" }, "subscriptionExpired": { - "message": "Subscription expired", + "message": "اشتراک به پایان رسیده است", "description": "The date header used when a subscription is past due." }, "pastDueWarningForChargeAutomatically": { - "message": "You have a grace period of $DAYS$ days from your subscription expiration date to maintain your subscription. Please resolve the past due invoices by $SUSPENSION_DATE$.", + "message": "شما یک دوره مهلت به مدت $DAYS$ روز از تاریخ انقضای اشتراک خود دارید تا اشتراک خود را حفظ کنید. لطفاً فاکتورهای معوق را تا تاریخ $SUSPENSION_DATE$ تسویه کنید.", "placeholders": { "days": { "content": "$1", @@ -9025,7 +9040,7 @@ "description": "A warning shown to the user when their subscription is past due and they are charged automatically." }, "pastDueWarningForSendInvoice": { - "message": "You have a grace period of $DAYS$ days from the date your first unpaid invoice is due to maintain your subscription. Please resolve the past due invoices by $SUSPENSION_DATE$.", + "message": "شما یک دوره مهلت به مدت $DAYS$ روز از تاریخ سررسید اولین فاکتور پرداخت نشده دارید تا اشتراک خود را حفظ کنید. لطفاً فاکتورهای معوق را تا تاریخ $SUSPENSION_DATE$ تسویه کنید.", "placeholders": { "days": { "content": "$1", @@ -9039,54 +9054,54 @@ "description": "A warning shown to the user when their subscription is past due and they pay via invoice." }, "unpaidInvoice": { - "message": "Unpaid invoice", + "message": "صورت حساب پرداخت نشده", "description": "The header of a warning box shown to a user whose subscription is unpaid." }, "toReactivateYourSubscription": { - "message": "To reactivate your subscription, please resolve the past due invoices.", + "message": "برای فعال‌سازی مجدد اشتراک خود، لطفاً فاکتورهای معوق را تسویه کنید.", "description": "The body of a warning box shown to a user whose subscription is unpaid." }, "cancellationDate": { - "message": "Cancellation date", + "message": "تاریخ لغو", "description": "The date header used when a subscription is cancelled." }, "machineAccountsCannotCreate": { - "message": "Machine accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." + "message": "حساب‌های ماشین را نمی‌توان در سازمان‌های معلق ایجاد کرد. لطفاً برای کمک با مالک سازمان خود تماس بگیرید." }, "machineAccount": { - "message": "Machine account", + "message": "حساب کاربری دستگاه", "description": "A machine user which can be used to automate processes and access secrets in the system." }, "machineAccounts": { - "message": "Machine accounts", + "message": "حساب‌های کاربری دستگاه", "description": "The title for the section that deals with machine accounts." }, "newMachineAccount": { - "message": "New machine account", + "message": "حساب کاربری دستگاه جدید", "description": "Title for creating a new machine account." }, "machineAccountsNoItemsMessage": { - "message": "Create a new machine account to get started automating secret access.", + "message": "برای شروع خودکارسازی دسترسی مخفی، یک حساب دستگاه جدید ایجاد کنید.", "description": "Message to encourage the user to start creating machine accounts." }, "machineAccountsNoItemsTitle": { - "message": "Nothing to show yet", + "message": "هنوز چیزی برای نشان دادن موجود نیست", "description": "Title to indicate that there are no machine accounts to display." }, "deleteMachineAccounts": { - "message": "Delete machine accounts", + "message": "حذف حساب‌های دستگاه", "description": "Title for the action to delete one or multiple machine accounts." }, "deleteMachineAccount": { - "message": "Delete machine account", + "message": "حذف حساب دستگاه", "description": "Title for the action to delete a single machine account." }, "viewMachineAccount": { - "message": "View machine account", + "message": "مشاهده حساب دستگاه", "description": "Action to view the details of a machine account." }, "deleteMachineAccountDialogMessage": { - "message": "Deleting machine account $MACHINE_ACCOUNT$ is permanent and irreversible.", + "message": "حذف حساب دستگاه $MACHINE_ACCOUNT$ دائمی و غیرقابل بازگشت است.", "placeholders": { "machine_account": { "content": "$1", @@ -9095,10 +9110,10 @@ } }, "deleteMachineAccountsDialogMessage": { - "message": "Deleting machine accounts is permanent and irreversible." + "message": "حذف حساب‌های ماشین، دائمی و غیرقابل برگشت است." }, "deleteMachineAccountsConfirmMessage": { - "message": "Delete $COUNT$ machine accounts", + "message": "حذف $COUNT$ حساب دستگاه", "placeholders": { "count": { "content": "$1", @@ -9107,60 +9122,60 @@ } }, "deleteMachineAccountToast": { - "message": "Machine account deleted" + "message": "حساب دستگاه حذف شد" }, "deleteMachineAccountsToast": { - "message": "Machine accounts deleted" + "message": "حساب‌های دستگاه حذف شد" }, "searchMachineAccounts": { - "message": "Search machine accounts", + "message": "جستجوی حساب‌های دستگاه", "description": "Placeholder text for searching machine accounts." }, "editMachineAccount": { - "message": "Edit machine account", + "message": "ویرایش حساب دستگاه", "description": "Title for editing a machine account." }, "machineAccountName": { - "message": "Machine account name", + "message": "نام حساب دستگاه", "description": "Label for the name of a machine account" }, "machineAccountCreated": { - "message": "Machine account created", + "message": "حساب دستگاه ایجاد شد", "description": "Notifies that a new machine account has been created" }, "machineAccountUpdated": { - "message": "Machine account updated", + "message": "حساب دستگاه به‌روزرسانی شد", "description": "Notifies that a machine account has been updated" }, "projectMachineAccountsDescription": { - "message": "Grant machine accounts access to this project." + "message": "دسترسی حساب‌های دستگاه به این پروژه را اعطا کنید." }, "projectMachineAccountsSelectHint": { - "message": "Type or select machine accounts" + "message": "حساب‌های دستگاه را وارد کنید یا انتخاب کنید" }, "projectEmptyMachineAccountAccessPolicies": { - "message": "Add machine accounts to grant access" + "message": "برای اعطای دسترسی، حساب‌های دستگاه را اضافه کنید" }, "machineAccountPeopleDescription": { - "message": "Grant groups or people access to this machine account." + "message": "به گروه‌ها یا افراد اجازه دسترسی به این حساب ماشین را بدهید." }, "machineAccountProjectsDescription": { - "message": "Assign projects to this machine account. " + "message": "پروژه‌ها را به این حساب دستگاه اختصاص دهید. " }, "createMachineAccount": { - "message": "Create a machine account" + "message": "ایجاد حساب دستگاه" }, "maPeopleWarningMessage": { - "message": "Removing people from a machine account does not remove the access tokens they created. For security best practice, it is recommended to revoke access tokens created by people removed from a machine account." + "message": "حذف افراد از حساب دستگاه، توکن‌های دسترسی ایجاد شده توسط آن‌ها را حذف نمی‌کند. برای بهترین عملکرد امنیتی، توصیه می‌شود توکن‌های دسترسی ایجاد شده توسط افرادی که از حساب دستگاه حذف شده‌اند لغو شود." }, "smAccessRemovalWarningMaTitle": { - "message": "Remove access to this machine account" + "message": "دسترسی به این حساب دستگاه را حذف کنید" }, "smAccessRemovalWarningMaMessage": { - "message": "This action will remove your access to the machine account." + "message": "این عمل دسترسی شما به حساب دستگاه را حذف می‌کند." }, "machineAccountsIncluded": { - "message": "$COUNT$ machine accounts included", + "message": "تعداد $COUNT$ حساب ماشین شامل شده است", "placeholders": { "count": { "content": "$1", @@ -9169,7 +9184,7 @@ } }, "additionalMachineAccountCost": { - "message": "$COST$ per month for additional machine accounts", + "message": "$COST$ در ماه برای هر حساب ماشین اضافی", "placeholders": { "cost": { "content": "$1", @@ -9178,10 +9193,10 @@ } }, "additionalMachineAccounts": { - "message": "Additional machine accounts" + "message": "حساب‌های ماشین اضافی" }, "includedMachineAccounts": { - "message": "Your plan comes with $COUNT$ machine accounts.", + "message": "طرح شما شامل $COUNT$ حساب ماشین است.", "placeholders": { "count": { "content": "$1", @@ -9190,7 +9205,7 @@ } }, "addAdditionalMachineAccounts": { - "message": "You can add additional machine accounts for $COST$ per month.", + "message": "می‌توانید دستگاه اضافی حساب‌ها را با $COST$ در ماه اضافه کنید.", "placeholders": { "cost": { "content": "$1", @@ -9199,31 +9214,31 @@ } }, "limitMachineAccounts": { - "message": "Limit machine accounts (optional)" + "message": "محدود کردن حساب‌های دستگاه (اختیاری)" }, "limitMachineAccountsDesc": { - "message": "Set a limit for your machine accounts. Once this limit is reached, you will not be able to create new machine accounts." + "message": "برای حساب‌های دستگاه خود محدودیتی تعیین کنید. پس از رسیدن به این محدودیت، نمی‌توانید حساب‌های دستگاه جدید ایجاد کنید." }, "machineAccountLimit": { - "message": "Machine account limit (optional)" + "message": "حداکثر تعداد حساب‌های ماشین (اختیاری)" }, "maxMachineAccountCost": { - "message": "Max potential machine account cost" + "message": "حداکثر هزینه ممکن برای حساب‌های دستگاه" }, "machineAccountAccessUpdated": { - "message": "Machine account access updated" + "message": "دسترسی حساب‌های دستگاه به‌روزرسانی شد" }, "restrictedGroupAccessDesc": { - "message": "You cannot add yourself to a group." + "message": "شما نمی‌توانید خودتان را به یک گروه اضافه کنید." }, "deleteProvider": { - "message": "Delete provider" + "message": "حذف ارائه‌دهنده" }, "deleteProviderConfirmation": { - "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." + "message": "حذف یک ارائه‌دهنده دائمی و غیرقابل بازگشت است. برای تأیید حذف ارائه‌دهنده و تمام داده‌های مرتبط، کلمه عبور اصلی خود را وارد کنید." }, "deleteProviderName": { - "message": "Cannot delete $ID$", + "message": "امکان حذف $ID$ وجود ندارد", "placeholders": { "id": { "content": "$1", @@ -9232,7 +9247,7 @@ } }, "deleteProviderWarningDescription": { - "message": "You must unlink all clients before you can delete $ID$.", + "message": "قبل از حذف $ID$ باید تمام مشتری‌ها را جدا کنید.", "placeholders": { "id": { "content": "$1", @@ -9241,96 +9256,96 @@ } }, "providerDeleted": { - "message": "Provider deleted" + "message": "ارائه‌دهنده حذف شد" }, "providerDeletedDesc": { - "message": "The Provider and all associated data has been deleted." + "message": "ارائه‌دهنده و تمام داده‌های مرتبط حذف شدند." }, "deleteProviderRecoverConfirmDesc": { - "message": "You have requested to delete this Provider. Use the button below to confirm." + "message": "شما درخواست حذف این ارائه‌دهنده را داده‌اید. برای تأیید، از دکمه زیر استفاده کنید." }, "deleteProviderWarning": { - "message": "Deleting your provider is permanent. It cannot be undone." + "message": "حذف ارائه‌دهنده شما دائمی است و قابل بازگشت نیست." }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "خطا در اختصاص مجموعه هدف." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "خطا در اختصاص پوشه هدف." }, "integrationsAndSdks": { - "message": "Integrations & SDKs", + "message": "ادغام‌ها و کیت‌های توسعه نرم‌افزار (SDK)", "description": "The title for the section that deals with integrations and SDKs." }, "integrations": { - "message": "Integrations" + "message": "یکپارچه سازی‌ها" }, "integrationsDesc": { - "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + "message": "همگام‌سازی خودکار رمزها از مدیر اسرار Bitwarden به سرویس‌های ثالث." }, "sdks": { - "message": "SDKs" + "message": "SDK ها" }, "sdksDesc": { - "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + "message": "برای ساخت برنامه‌های خود، از کیت توسعه مدیر اسرار (SDK) Bitwarden در زبان‌های برنامه‌نویسی زیر استفاده کنید." }, "ssoDescStart": { - "message": "Configure", + "message": "پیکربندی", "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, "ssoDescEnd": { - "message": "for Bitwarden using the implementation guide for your Identity Provider.", + "message": "برای Bitwarden با استفاده از راهنمای پیاده‌سازی ارائه‌دهنده هویت خود.", "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure single sign-on for Bitwarden using the implementation guide for your Identity Provider." }, "userProvisioning": { - "message": "User provisioning" + "message": "تأمین کاربران" }, "scimIntegration": { "message": "SCIM" }, "scimIntegrationDescStart": { - "message": "Configure ", + "message": "پیکربندی ", "description": "This represents the beginning of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "scimIntegrationDescEnd": { - "message": "(System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider.", + "message": "سامانه مدیریت هویت بین‌دامنه (Scim) برای تأمین خودکار کاربران و گروه‌ها در Bitwarden با استفاده از راهنمای پیاده‌سازی ارائه‌دهنده هویت شما.", "description": "This represents the end of a sentence, broken up to include links. The full sentence will be 'Configure SCIM (System for Cross-domain Identity Management) to automatically provision users and groups to Bitwarden using the implementation guide for your Identity Provider" }, "bwdc": { - "message": "Bitwarden Directory Connector" + "message": "کانکتور دایرکتوری Bitwarden" }, "bwdcDesc": { - "message": "Configure Bitwarden Directory Connector to automatically provision users and groups using the implementation guide for your Identity Provider." + "message": "کانکتور دایرکتوری Bitwarden را برای تأمین خودکار کاربران و گروه‌ها با استفاده از راهنمای پیاده‌سازی ارائه‌دهنده هویت خود تنظیم کنید." }, "eventManagement": { - "message": "Event management" + "message": "مدیریت رویدادها" }, "eventManagementDesc": { - "message": "Integrate Bitwarden event logs with your SIEM (system information and event management) system by using the implementation guide for your platform." + "message": "گزارش‌های رویداد Bitwarden را با سیستم SIEM (مدیریت اطلاعات و رویدادهای امنیتی) خود با استفاده از راهنمای پیاده‌سازی مخصوص پلتفرم خود ادغام کنید." }, "deviceManagement": { - "message": "Device management" + "message": "مدیریت دستگاه" }, "deviceManagementDesc": { - "message": "Configure device management for Bitwarden using the implementation guide for your platform." + "message": "مدیریت دستگاه‌ها را برای Bitwarden با استفاده از راهنمای پیاده‌سازی مربوط به پلتفرم خود پیکربندی کنید." }, "deviceIdMissing": { - "message": "Device ID is missing" + "message": "شناسه دستگاه موجود نیست" }, "deviceTypeMissing": { - "message": "Device type is missing" + "message": "نوع دستگاه مشخص نیست" }, "deviceCreationDateMissing": { - "message": "Device creation date is missing" + "message": "تاریخ ایجاد دستگاه مشخص نیست" }, "desktopRequired": { - "message": "Desktop required" + "message": "دسکتاپ مورد نیاز است" }, "reopenLinkOnDesktop": { - "message": "Reopen this link from your email on a desktop." + "message": "این لینک را از ایمیل خود روی دسکتاپ باز کنید." }, "integrationCardTooltip": { - "message": "Launch $INTEGRATION$ implementation guide.", + "message": "راهنمای پیاده‌سازی $INTEGRATION$ را اجرا کنید.", "placeholders": { "integration": { "content": "$1", @@ -9339,7 +9354,7 @@ } }, "smIntegrationTooltip": { - "message": "Set up $INTEGRATION$.", + "message": "راه‌اندازی $INTEGRATION$.", "placeholders": { "integration": { "content": "$1", @@ -9348,7 +9363,7 @@ } }, "smSdkTooltip": { - "message": "View $SDK$ repository", + "message": "مشاهده مخزن $SDK$", "placeholders": { "sdk": { "content": "$1", @@ -9357,7 +9372,7 @@ } }, "integrationCardAriaLabel": { - "message": "open $INTEGRATION$ implementation guide in a new tab.", + "message": "راهنمای پیاده‌سازی $INTEGRATION$ را در یک تب جدید باز کنید.", "placeholders": { "integration": { "content": "$1", @@ -9366,7 +9381,7 @@ } }, "smSdkAriaLabel": { - "message": "view $SDK$ repository in a new tab.", + "message": "مخزن $SDK$ را در یک تب جدید مشاهده کنید.", "placeholders": { "sdk": { "content": "$1", @@ -9375,7 +9390,7 @@ } }, "smIntegrationCardAriaLabel": { - "message": "set up $INTEGRATION$ implementation guide in a new tab.", + "message": "راهنمای پیاده‌سازی $INTEGRATION$ را در یک تب جدید راه‌اندازی کنید.", "placeholders": { "integration": { "content": "$1", @@ -9384,76 +9399,76 @@ } }, "createNewClientToManageAsProvider": { - "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + "message": "یک سازمان مشتری جدید به‌عنوان ارائه‌دهنده ایجاد کنید. صندلی‌های اضافی در دوره صورتحساب بعدی اعمال خواهند شد." }, "selectAPlan": { - "message": "Select a plan" + "message": "یک طرح انتخاب کنید" }, "thirtyFivePercentDiscount": { - "message": "35% Discount" + "message": "۳۵٪ تخفیف" }, "monthPerMember": { - "message": "month per member" + "message": "ماهانه به ازای هر عضو" }, "monthPerMemberBilledAnnually": { - "message": "month per member billed annually" + "message": "ماهانه به ازای هر عضو، پرداخت به صورت سالانه" }, "seats": { - "message": "Seats" + "message": "صندلی‌ها" }, "addOrganization": { - "message": "Add organization" + "message": "افزودن سازمان" }, "createdNewClient": { - "message": "Successfully created new client" + "message": "ایجاد مشتری جدید با موفقیت انجام شد" }, "noAccess": { - "message": "No access" + "message": "بدون دسترسی" }, "collectionAdminConsoleManaged": { - "message": "This collection is only accessible from the admin console" + "message": "این مجموعه فقط از طریق کنسول مدیریت قابل دسترسی است" }, "organizationOptionsMenu": { - "message": "Toggle Organization Menu" + "message": "تغییر وضعیت منوی سازمان" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "مورد گاوصندوق را انتخاب کنید" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "مورد مجموعه را انتخاب کنید" }, "manageBillingFromProviderPortalMessage": { - "message": "Manage billing from the Provider Portal" + "message": "مدیریت صورتحساب از طریق درگاه ارائه‌دهنده" }, "continueSettingUp": { - "message": "Continue setting up Bitwarden" + "message": "ادامه راه‌اندازی Bitwarden" }, "continueSettingUpFreeTrial": { - "message": "Continue setting up your free trial of Bitwarden" + "message": "ادامه تنظیم دوره آزمایشی رایگان Bitwarden خود را انجام دهید" }, "continueSettingUpPasswordManager": { - "message": "Continue setting up Bitwarden Password Manager" + "message": "ادامه راه‌اندازی مدیر کلمه عبور Bitwarden را انجام دهید" }, "continueSettingUpFreeTrialPasswordManager": { - "message": "Continue setting up your free trial of Bitwarden Password Manager" + "message": "ادامه راه‌اندازی دوره آزمایشی رایگان مدیر کلمه عبور Bitwarden را انجام دهید" }, "continueSettingUpSecretsManager": { - "message": "Continue setting up Bitwarden Secrets Manager" + "message": "ادامه راه‌اندازی مدیر اسرار Bitwarden را انجام دهید" }, "continueSettingUpFreeTrialSecretsManager": { - "message": "Continue setting up your free trial of Bitwarden Secrets Manager" + "message": "ادامه راه‌اندازی دوره آزمایشی رایگان مدیر اسرار Bitwarden را انجام دهید" }, "enterTeamsOrgInfo": { - "message": "Enter your Teams organization information" + "message": "اطلاعات سازمان تیم خود را وارد کنید" }, "enterFamiliesOrgInfo": { - "message": "Enter your Families organization information" + "message": "اطلاعات سازمان خانواده خود را وارد کنید" }, "enterEnterpriseOrgInfo": { - "message": "Enter your Enterprise organization information" + "message": "اطلاعات سازمان تجاری خود را وارد کنید" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "مشاهده موارد در $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -9463,7 +9478,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "بازگشت به $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -9473,11 +9488,11 @@ } }, "back": { - "message": "Back", + "message": "بازگشت", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "حذف $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -9487,34 +9502,34 @@ } }, "viewInfo": { - "message": "View info" + "message": "نمایش اطلاعات" }, "viewAccess": { - "message": "View access" + "message": "مشاهده دسترسی" }, "noCollectionsSelected": { - "message": "You have not selected any collections." + "message": "شما هیچ مجموعه‌ای را انتخاب نکرده‌اید." }, "updateName": { - "message": "Update name" + "message": "به‌روزرسانی نام" }, "updatedOrganizationName": { - "message": "Updated organization name" + "message": "نام سازمان به‌روزرسانی شد" }, "providerPlan": { - "message": "Managed Service Provider" + "message": "ارائه‌دهنده خدمات مدیریت شده" }, "managedServiceProvider": { - "message": "Managed service provider" + "message": "ارائه‌دهنده خدمات مدیریت شده" }, "multiOrganizationEnterprise": { - "message": "Multi-organization enterprise" + "message": "سازمان تجاری چندسازمانی" }, "orgSeats": { - "message": "Organization Seats" + "message": "صندلی‌های سازمان" }, "providerDiscount": { - "message": "$AMOUNT$% Discount", + "message": "$AMOUNT$٪ تخفیف", "placeholders": { "amount": { "content": "$1", @@ -9523,122 +9538,122 @@ } }, "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." + "message": "تعداد تکرارهای KDF پایین است. برای افزایش امنیت حساب کاربری خود، تعداد تکرارها را افزایش دهید." }, "changeKDFSettings": { - "message": "Change KDF settings" + "message": "تغییر تنظیمات KDF" }, "secureYourInfrastructure": { - "message": "Secure your infrastructure" + "message": "زیرساخت خود را ایمن کنید" }, "protectYourFamilyOrBusiness": { - "message": "Protect your family or business" + "message": "از خانواده یا کسب‌وکار خود را محافظت کنید" }, "upgradeOrganizationCloseSecurityGaps": { - "message": "Close security gaps with monitoring reports" + "message": "شکاف‌های امنیتی را با گزارش‌های نظارتی ببندید" }, "upgradeOrganizationCloseSecurityGapsDesc": { - "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + "message": "با ارتقا به طرح پرداختی برای نظارت پیشرفته، از آسیب‌پذیری‌های امنیتی جلوتر باشید." }, "approveAllRequests": { - "message": "Approve all requests" + "message": "تمام درخواست‌ها را تأیید کن" }, "allLoginRequestsApproved": { - "message": "All login requests approved" + "message": "تمام درخواست‌های ورود تأیید شدند" }, "payPal": { - "message": "PayPal" + "message": "پی پال" }, "bitcoin": { - "message": "Bitcoin" + "message": "بیت کوین" }, "updatedTaxInformation": { - "message": "Updated tax information" + "message": "اطلاعات مالیاتی به‌روزرسانی شد" }, "billingInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "شناسه مالیاتی نامعتبر است. اگر فکر می‌کنید این یک اشتباه است، لطفاً با پشتیبانی تماس بگیرید." }, "billingTaxIdTypeInferenceError": { - "message": "We were unable to validate your tax ID, if you believe this is an error please contact support." + "message": "ما نتوانستیم شناسه مالیاتی شما را تأیید کنیم. اگر فکر می‌کنید این یک اشتباه است، لطفاً با پشتیبانی تماس بگیرید." }, "billingPreviewInvalidTaxIdError": { - "message": "Invalid tax ID, if you believe this is an error please contact support." + "message": "شناسه مالیاتی نامعتبر است. اگر فکر می‌کنید این یک اشتباه است، لطفاً با پشتیبانی تماس بگیرید." }, "billingPreviewInvoiceError": { - "message": "An error occurred while previewing the invoice. Please try again later." + "message": "هنگام پیش‌نمایش فاکتور خطا رخ داد. لطفاً بعداً دوباره تلاش کنید." }, "unverified": { - "message": "Unverified" + "message": "تأیید نشده" }, "verified": { - "message": "Verified" + "message": "تأیید شده" }, "viewSecret": { - "message": "View secret" + "message": "مشاهده راز" }, "noClients": { - "message": "There are no clients to list" + "message": "هیچ مشتری برای نمایش وجود ندارد" }, "providerBillingEmailHint": { - "message": "This email address will receive all invoices pertaining to this provider", + "message": "این آدرس ایمیل همه فاکتورهای مربوط به این ارائه‌دهنده را دریافت خواهد کرد", "description": "A hint that shows up on the Provider setup page to inform the admin the billing email will receive the provider's invoices." }, "upgradeOrganizationEnterprise": { - "message": "Identify security risks by auditing member access" + "message": "شناسایی خطرات امنیتی با بررسی دسترسی اعضا" }, "onlyAvailableForEnterpriseOrganization": { - "message": "Quickly view member access across the organization by upgrading to an Enterprise plan." + "message": "با ارتقا به طرح سازمانی، به‌سرعت دسترسی اعضا در سراسر سازمان را مشاهده کنید." }, "date": { - "message": "Date" + "message": "تاریخ" }, "exportClientReport": { - "message": "Export client report" + "message": "گزارشات برون ریزی مشتری" }, "memberAccessReport": { - "message": "Member access" + "message": "دسترسی اعضا" }, "memberAccessReportDesc": { - "message": "Ensure members have access to the right credentials and their accounts are secure. Use this report to obtain a CSV of member access and account configurations." + "message": "اطمینان حاصل کنید که اعضا به اطلاعات ورود مناسب دسترسی دارند و حساب‌هایشان امن است. از این گزارش برای دریافت پرونده CSV شامل دسترسی اعضا و تنظیمات حساب‌های کاربری استفاده کنید." }, "memberAccessReportPageDesc": { - "message": "Audit organization member access across groups, collections, and collection items. The CSV export provides a detailed breakdown per member, including information on collection permissions and account configurations." + "message": "دسترسی اعضای سازمان را در گروه‌ها، مجموعه‌ها و موارد مجموعه بررسی کنید. خروجی CSV شامل جزئیات کامل برای هر عضو است، از جمله اطلاعات مربوط به مجوزهای مجموعه و تنظیمات حساب کاربری." }, "memberAccessReportNoCollection": { - "message": "(No Collection)" + "message": "(بدون مجموعه)" }, "memberAccessReportNoCollectionPermission": { - "message": "(No Collection Permission)" + "message": "(بدون مجوز مجموعه)" }, "memberAccessReportNoGroup": { - "message": "(No Group)" + "message": "(بدون گروه)" }, "memberAccessReportTwoFactorEnabledTrue": { - "message": "On" + "message": "روشن" }, "memberAccessReportTwoFactorEnabledFalse": { - "message": "Off" + "message": "خاموش" }, "memberAccessReportAuthenticationEnabledTrue": { - "message": "On" + "message": "روشن" }, "memberAccessReportAuthenticationEnabledFalse": { - "message": "Off" + "message": "خاموش" }, "higherKDFIterations": { - "message": "Higher KDF iterations can help protect your master password from being brute forced by an attacker." + "message": "افزایش تعداد تکرارهای KDF می‌تواند به محافظت از کلمه عبور اصلی شما در برابر حملات بروت‌فورس کمک کند." }, "incrementsOf100,000": { - "message": "increments of 100,000" + "message": "افزایش‌ها به صورت ۱۰۰,۰۰۰ تایی" }, "smallIncrements": { - "message": "small increments" + "message": "افزایش‌های کوچک" }, "kdfIterationRecommends": { - "message": "We recommend 600,000 or more" + "message": "ما توصیه می‌کنیم ۶۰۰,۰۰۰ یا بیشتر باشد" }, "kdfToHighWarningIncreaseInIncrements": { - "message": "For older devices, setting your KDF too high may lead to performance issues. Increase the value in $VALUE$ and test your devices.", + "message": "برای دستگاه‌های قدیمی‌تر، تنظیم KDF خیلی بالا ممکن است باعث مشکلات عملکردی شود. مقدار را در $VALUE$ افزایش دهید و دستگاه‌های خود را تست کنید.", "placeholders": { "value": { "content": "$1", @@ -9647,31 +9662,31 @@ } }, "providerReinstate": { - "message": " Contact Customer Support to reinstate your subscription." + "message": " برای بازیابی اشتراک خود با پشتیبانی مشتری تماس بگیرید." }, "secretPeopleDescription": { - "message": "Grant groups or people access to this secret. Permissions set for people will override permissions set by groups." + "message": "به گروه‌ها یا افراد برای دسترسی به این راز اجازه دهید. مجوزهایی که برای افراد تنظیم می‌شود، بر مجوزهای تنظیم‌شده توسط گروه‌ها اولویت خواهد داشت." }, "secretPeopleEmptyMessage": { - "message": "Add people or groups to share access to this secret" + "message": "برای اشتراک‌گذاری دسترسی به این راز، افراد یا گروه‌ها را اضافه کنید" }, "secretMachineAccountsDescription": { - "message": "Grant machine accounts access to this secret." + "message": "به حساب‌های دستگاه اجازه دسترسی به این راز را بدهید." }, "secretMachineAccountsEmptyMessage": { - "message": "Add machine accounts to grant access to this secret" + "message": "برای اعطای دسترسی به این راز، حساب‌های دستگاه را اضافه کنید" }, "smAccessRemovalWarningSecretTitle": { - "message": "Remove access to this secret" + "message": "دسترسی به این راز را حذف کنید" }, "smAccessRemovalSecretMessage": { - "message": "This action will remove your access to this secret." + "message": "این اقدام دسترسی شما به این راز را حذف خواهد کرد." }, "invoice": { - "message": "Invoice" + "message": "صورت‌حساب" }, "unassignedSeatsAvailable": { - "message": "You have $SEATS$ unassigned seats available.", + "message": "شما $SEATS$ صندلی اختصاص نیافته در دسترس دارید.", "placeholders": { "seats": { "content": "$1", @@ -9681,61 +9696,61 @@ "description": "A message showing how many unassigned seats are available for a provider." }, "contactYourProviderForAdditionalSeats": { - "message": "Contact your provider admin to purchase additional seats." + "message": "برای خرید صندلی‌های اضافی با مدیر ارائه‌دهنده خود تماس بگیرید." }, "open": { - "message": "Open", + "message": "باز کن", "description": "The status of an invoice." }, "uncollectible": { - "message": "Uncollectible", + "message": "غیرقابل وصول", "description": "The status of an invoice." }, "clientDetails": { - "message": "Client details" + "message": "جزئیات مشتری" }, "downloadCSV": { - "message": "Download CSV" + "message": "بارگیری CSV" }, "monthlySubscriptionUserSeatsMessage": { - "message": "Adjustments to your subscription will result in prorated charges to your billing totals on your next billing period. " + "message": "تغییرات در اشتراک شما منجر به اعمال هزینه‌های نسبت‌ داده شده در مجموع صورتحساب دوره بعدی خواهد شد. " }, "annualSubscriptionUserSeatsMessage": { - "message": "Adjustments to your subscription will result in prorated charges on a monthly billing cycle. " + "message": "تغییرات در اشتراک شما منجر به اعمال هزینه‌های نسبت‌داده شده در دوره صورتحساب ماهانه خواهد شد. " }, "billingHistoryDescription": { - "message": "Download a CSV to obtain client details for each billing date. Prorated charges are not included in the CSV and may vary from the linked invoice. For the most accurate billing details, refer to your monthly invoices.", + "message": "یک پرونده CSV بارگیری کنید تا جزئیات مشتری‌ها را برای هر تاریخ صورتحساب دریافت کنید. هزینه‌های نسبت‌داده شده در CSV گنجانده نشده‌اند و ممکن است با فاکتور مربوطه متفاوت باشند. برای دقیق‌ترین اطلاعات صورتحساب، به فاکتورهای ماهانه خود مراجعه کنید.", "description": "A paragraph on the Billing History page of the Provider Portal letting users know they can download a CSV report for their invoices that does not include prorations." }, "noInvoicesToList": { - "message": "There are no invoices to list", + "message": "هیچ فاکتوری برای نمایش وجود ندارد", "description": "A paragraph on the Billing History page of the Provider Portal letting users know they can download a CSV report for their invoices that does not include prorations." }, "providerClientVaultPrivacyNotification": { - "message": "Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions,", + "message": "اطلاعیه: اواخر این ماه، حریم خصوصی گاوصندوق‌های مشتری بهبود می‌یابد و اعضای ارائه‌دهنده دیگر به طور مستقیم به موارد گاوصندوق مشتری دسترسی نخواهند داشت. برای سوالات،", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions, please contact Bitwarden support'." }, "contactBitwardenSupport": { - "message": "contact Bitwarden support.", + "message": "پیام به پشتیبانی Bitwarden.", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'Notice: Later this month, client vault privacy will be improved and provider members will no longer have direct access to client vault items. For questions, please contact Bitwarden support'. 'Bitwarden' should not be translated" }, "sponsored": { - "message": "Sponsored" + "message": "حمایت شده" }, "licenseAndBillingManagementDesc": { - "message": "After making updates in the Bitwarden cloud server, upload your license file to apply the most recent changes." + "message": "پس از انجام به‌روزرسانی‌ها در سرور ابری Bitwarden، برای اعمال آخرین تغییرات، فایل لایسنس خود را بارگذاری کنید." }, "addToFolder": { - "message": "Add to folder" + "message": "افزودن به پوشه" }, "selectFolder": { - "message": "Select folder" + "message": "انتخاب پوشه" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "۱ مورد به طور دائمی به سازمان انتخاب شده منتقل خواهد شد. شما دیگر مالک این مورد نخواهید بود." }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ مورد به طور دائمی به سازمان انتخاب شده منتقل خواهند شد. شما دیگر مالک این موارد نخواهید بود.", "placeholders": { "personal_items_count": { "content": "$1", @@ -9744,7 +9759,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "۱ مورد به طور دائمی به $ORG$ منتقل خواهد شد. شما دیگر مالک این مورد نخواهید بود.", "placeholders": { "org": { "content": "$1", @@ -9753,7 +9768,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ مورد به طور دائمی به $ORG$ منتقل خواهند شد. شما دیگر مالک این موارد نخواهید بود.", "placeholders": { "personal_items_count": { "content": "$1", @@ -9766,85 +9781,85 @@ } }, "data": { - "message": "Data" + "message": "داده‌" }, "purchasedSeatsRemoved": { - "message": "purchased seats removed" + "message": "صندلی‌های خریداری‌شده حذف شدند" }, "environmentVariables": { - "message": "Environment variables" + "message": "متغیرهای محیطی" }, "organizationId": { - "message": "Organization ID" + "message": "شناسه سازمان" }, "projectIds": { - "message": "Project IDs" + "message": "شناسه‌های پروژه" }, "projectId": { - "message": "Project ID" + "message": "شناسه پروژه" }, "projectsAccessedByMachineAccount": { - "message": "The following projects can be accessed by this machine account." + "message": "این حساب دستگاه می‌تواند به پروژه‌های زیر دسترسی داشته باشد." }, "config": { - "message": "Config" + "message": "پیکربندی" }, "learnMoreAboutEmergencyAccess": { - "message": "Learn more about emergency access" + "message": "بیشتر درباره دسترسی اضطراری بیاموزید" }, "learnMoreAboutMatchDetection": { - "message": "Learn more about match detection" + "message": "بیشتر درباره تشخیص تطابق بیاموزید" }, "learnMoreAboutMasterPasswordReprompt": { - "message": "Learn more about master password re-prompt" + "message": "بیشتر درباره درخواست مجدد کلمه عبور اصلی بیاموزید" }, "learnMoreAboutSearchingYourVault": { - "message": "Learn more about searching your vault" + "message": "بیشتر درباره جستجو در گاوصندوق خود بیاموزید" }, "learnMoreAboutYourAccountFingerprintPhrase": { - "message": "Learn about your account fingerprint phrase" + "message": "درباره عبارت اثر انگشت حساب خود اطلاعات کسب کنید" }, "impactOfRotatingYourEncryptionKey": { - "message": "Impact of rotating your encryption key" + "message": "تأثیر تغییر دوره‌ای کلید رمزگذاری شما" }, "learnMoreAboutEncryptionAlgorithms": { - "message": "Learn more about encryption algorithms" + "message": "بیشتر درباره الگوریتم‌های رمزگذاری بیاموزید" }, "learnMoreAboutKDFIterations": { - "message": "Learn more about KDF iterations" + "message": "بیشتر درباره تکرارهای KDF بیاموزید" }, "learnMoreAboutLocalization": { - "message": "Learn more about localization" + "message": "بیشتر درباره بومی‌سازی بیاموزید" }, "learnMoreAboutWebsiteIcons": { - "message": "Learn more about using website icons" + "message": "بیشتر درباره استفاده از آیکون‌های وب‌سایت بیاموزید" }, "learnMoreAboutUserAccess": { - "message": "Learn more about user access" + "message": "بیشتر درباره دسترسی کاربران بیاموزید" }, "learnMoreAboutMemberRoles": { - "message": "Learn more about member roles and permissions" + "message": "بیشتر درباره نقش‌ها و مجوزهای اعضا بیاموزید" }, "whatIsACvvNumber": { - "message": "What is a CVV number?" + "message": "عدد CVV چیست؟" }, "learnMoreAboutApi": { - "message": "Learn more about Bitwarden's API" + "message": "بیشتر درباره Bitwarden API بیاموزید" }, "fileSend": { - "message": "File Send" + "message": "پرونده ارسال" }, "fileSends": { - "message": "File Sends" + "message": "پرونده ارسال‌ها" }, "textSend": { - "message": "Text Send" + "message": "ارسال متن" }, "textSends": { - "message": "Text Sends" + "message": "ارسال‌های متن" }, "includesXMembers": { - "message": "for $COUNT$ member", + "message": "برای $COUNT$ عضو", "placeholders": { "count": { "content": "$1", @@ -9862,10 +9877,10 @@ } }, "optionalOnPremHosting": { - "message": "Optional on-premises hosting" + "message": "میزبانی اختیاری در محل" }, "upgradeFreeOrganization": { - "message": "Upgrade your $NAME$ organization ", + "message": "سازمان $NAME$ خود را ارتقا دهید ", "placeholders": { "name": { "content": "$1", @@ -9874,10 +9889,10 @@ } }, "includeSsoAuthenticationMessage": { - "message": "SSO Authentication" + "message": "احراز هویت SSO" }, "familiesPlanInvLimitReachedManageBilling": { - "message": "Families organizations may have up to $SEATCOUNT$ members. Upgrade to a paid plan to invite more members.", + "message": "سازمان‌های خانواده ممکن است تا $SEATCOUNT$ عضو داشته باشند. برای دعوت از اعضای بیشتر، به یک طرح پولی ارتقا دهید.", "placeholders": { "seatcount": { "content": "$1", @@ -9886,7 +9901,7 @@ } }, "familiesPlanInvLimitReachedNoManageBilling": { - "message": "Families organizations may have up to $SEATCOUNT$ members. Contact your organization owner to upgrade.", + "message": "سازمان‌های خانواده ممکن است تا $SEATCOUNT$ عضو داشته باشند. برای ارتقا با مالک سازمان خود تماس بگیرید.", "placeholders": { "seatcount": { "content": "$1", @@ -9895,10 +9910,10 @@ } }, "upgradePlans": { - "message": "Upgrade your plan to invite members and experience powerful security features." + "message": "طرح خود را ارتقا دهید تا اعضا را دعوت کرده و از ویژگی‌های امنیتی قدرتمند بهره‌مند شوید." }, "upgradeDiscount": { - "message": "Save $AMOUNT$%", + "message": "$AMOUNT$٪ صرفه‌جویی کنید", "placeholders": { "amount": { "content": "$1", @@ -9907,49 +9922,49 @@ } }, "enterprisePlanUpgradeMessage": { - "message": "Advanced capabilities for larger organizations" + "message": "قابلیت‌های پیشرفته برای سازمان‌های بزرگ‌تر" }, "teamsPlanUpgradeMessage": { - "message": "Resilient protection for growing teams" + "message": "محافظت مقاوم برای تیم‌های در حال رشد" }, "teamsInviteMessage": { - "message": "Invite unlimited members" + "message": "دعوت از اعضا بدون محدودیت" }, "accessToCreateGroups": { - "message": "Access to create groups" + "message": "دسترسی برای ایجاد گروه‌ها" }, "syncGroupsAndUsersFromDirectory": { - "message": "Sync groups and users from a directory" + "message": "همگام‌سازی گروه‌ها و کاربران از یک دایرکتوری" }, "familyPlanUpgradeMessage": { - "message": "Secure your family logins" + "message": "ورودهای خانواده خود را ایمن کنید" }, "accessToPremiumFeatures": { - "message": "Access to Premium features" + "message": "دسترسی به ویژگی‌های پرمیوم" }, "additionalStorageGbMessage": { - "message": "GB additional storage" + "message": "گیگابایت فضای ذخیره‌سازی اضافی" }, "sshKeyAlgorithm": { - "message": "Key algorithm" + "message": "الگوریتم کلید" }, "sshPrivateKey": { - "message": "Private key" + "message": "کلید خصوصی" }, "sshPublicKey": { - "message": "Public key" + "message": "کلید عمومی" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "اثر انگشت" }, "sshKeyFingerprint": { - "message": "Fingerprint" + "message": "اثر انگشت" }, "sshKeyPrivateKey": { - "message": "Private key" + "message": "کلید خصوصی" }, "sshKeyPublicKey": { - "message": "Public key" + "message": "کلید عمومی" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -9964,89 +9979,89 @@ "message": "RSA 4096-Bit" }, "premiumAccounts": { - "message": "6 premium accounts" + "message": "۶ حساب پرمیوم" }, "unlimitedSharing": { - "message": "Unlimited sharing" + "message": "اشتراک‌گذاری نامحدود" }, "unlimitedCollections": { - "message": "Unlimited collections" + "message": "مجموعه‌های نامحدود" }, "secureDataSharing": { - "message": "Secure data sharing" + "message": "اشتراک‌گذاری امن داده‌ها" }, "eventLogMonitoring": { - "message": "Event log monitoring" + "message": "نظارت بر گزارش رویدادها" }, "directoryIntegration": { - "message": "Directory integration" + "message": "ادغام دایرکتوری" }, "passwordLessSso": { - "message": "Passwordless SSO" + "message": "ورود یک مرحله‌ای بدون کلمه عبور" }, "accountRecovery": { - "message": "Account recovery" + "message": "بازیابی حساب کاربری" }, "customRoles": { - "message": "Custom roles" + "message": "نقش‌های سفارشی" }, "unlimitedSecretsStorage": { - "message": "Unlimited secrets storage" + "message": "ذخیره‌سازی نامحدود رازها" }, "unlimitedUsers": { - "message": "Unlimited users" + "message": "کاربران نامحدود" }, "UpTo50MachineAccounts": { - "message": "Up to 50 machine accounts" + "message": "تا ۵۰ حساب ماشین" }, "UpTo20MachineAccounts": { - "message": "Up to 20 machine accounts" + "message": "تا ۲۰ حساب ماشین" }, "current": { - "message": "Current" + "message": "فعلی" }, "secretsManagerSubscriptionInfo": { - "message": "Your Secrets Manager subscription will upgrade based on the plan selected" + "message": "اشتراک مدیر اسرار شما بر اساس طرح انتخاب شده ارتقا خواهد یافت" }, "bitwardenPasswordManager": { - "message": "Bitwarden Password Manager" + "message": "مدیر کلمه عبور Bitwarden" }, "secretsManagerComplimentaryPasswordManager": { - "message": "Your complimentary one year Password Manager subscription will upgrade to the selected plan. You will not be charged until the complimentary period is over." + "message": "اشتراک رایگان یک‌ساله مدیر کلمه عبور شما به طرح انتخاب شده ارتقا خواهد یافت. تا پایان دوره رایگان هیچ هزینه‌ای از شما دریافت نخواهد شد." }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "پرونده در دستگاه ذخیره شد. از بخش بارگیری‌های دستگاه خود مدیریت کنید." }, "publicApi": { - "message": "Public API", + "message": "API عمومی", "description": "The text, 'API', is an acronym and should not be translated." }, "showCharacterCount": { - "message": "Show character count" + "message": "نمایش تعداد کاراکترها" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "مخفی کردن تعداد کاراکترها" }, "editAccess": { - "message": "Edit access" + "message": "ویرایش دسترسی" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "برای داده‌هایی مانند سوالات امنیتی از فیلدهای متنی استفاده کنید" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "برای داده‌های حساس مانند کلمه عبور از فیلدهای مخفی استفاده کنید" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "اگر می‌خواهید فیلدهای تیک‌دار فرم را به‌صورت خودکار پر کنید، مانند گزینه به یاد سپردن ایمیل، از کادرهای انتخاب استفاده کنید" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "وقتی در پر کردن خودکار برای یک وب‌سایت خاص به مشکل برخوردید، از فیلد مرتبط استفاده کنید." }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "شناسه Html، نام، aria-label یا محل نگهدار فیلد را وارد کنید." }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "شامل حروف بزرگ باشد", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -10054,7 +10069,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "شامل حروف کوچک باشد", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -10062,7 +10077,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "شامل اعداد", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -10070,36 +10085,36 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "افزودن کاراکترهای خاص", "description": "Full description for the password generator special characters checkbox" }, "addAttachment": { - "message": "Add attachment" + "message": "افزودن پیوست" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "بیشترین حجم پرونده ۵۰۰ مگابایت است" }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "آیا مطمئن هستید که می‌خواهید این پرونده پیوست را به‌طور دائمی حذف کنید؟" }, "manageSubscriptionFromThe": { - "message": "Manage subscription from the", + "message": "مدیریت اشتراک از طریق", "description": "This represents the beginning of a sentence. The full sentence will be 'Manage subscription from the Provider Portal', but 'Provider Portal' will be a link and thus cannot be included in the translation file." }, "toHostBitwardenOnYourOwnServer": { - "message": "To host Bitwarden on your own server, you will need to upload your license file. To support Free Families plans and advanced billing capabilities for your self-hosted organization, you will need to set up automatic sync in your self-hosted organization." + "message": "برای میزبانی Bitwarden روی سرور خود، باید پرونده لایسنس خود را بارگذاری کنید. برای پشتیبانی از طرح‌های رایگان خانواده‌ها و قابلیت‌های پیشرفته صورتحساب برای سازمان خودمیزبان، لازم است همگام‌سازی خودکار را در سازمان خودمیزبان خود راه‌اندازی کنید." }, "selfHostingTitleProper": { - "message": "Self-Hosting" + "message": "خود میزبانی" }, "claim-domain-single-org-warning": { - "message": "Claiming a domain will turn on the single organization policy." + "message": "ثبت دامنه، سیاست سازمان واحد را فعال می‌کند." }, "single-org-revoked-user-warning": { - "message": "Non-compliant members will be revoked. Administrators can restore members once they leave all other organizations." + "message": "اعضای غیرمنطبق دسترسی‌شان لغو خواهد شد. مدیران می‌توانند پس از خروج اعضا از سایر سازمان‌ها، آنها را مجدداً فعال کنند." }, "deleteOrganizationUser": { - "message": "Delete $NAME$", + "message": "حذف $NAME$", "placeholders": { "name": { "content": "$1", @@ -10109,7 +10124,7 @@ } }, "deleteOrganizationUserWarningDesc": { - "message": "This will permanently delete all items owned by $NAME$. Collection items are not impacted.", + "message": "این عمل تمام موارد متعلق به $NAME$ را به‌صورت دائمی حذف می‌کند. موارد مجموعه تحت تأثیر قرار نمی‌گیرند.", "description": "Warning description for the delete organization user dialog", "placeholders": { "name": { @@ -10119,11 +10134,11 @@ } }, "deleteManyOrganizationUsersWarningDesc": { - "message": "This will permanently delete all items owned by the following members. Collection items are not impacted.", + "message": "این عمل تمام موارد متعلق به اعضای زیر را به‌صورت دائمی حذف می‌کند. موارد مجموعه تحت تأثیر قرار نمی‌گیرند.", "description": "Warning description for the bulk delete organization users dialog" }, "organizationUserDeleted": { - "message": "Deleted $NAME$", + "message": "$NAME$ حذف شد", "placeholders": { "name": { "content": "$1", @@ -10132,10 +10147,10 @@ } }, "organizationUserDeletedDesc": { - "message": "The user was removed from the organization and all associated user data has been deleted." + "message": "کاربر از سازمان حذف شده و تمامی داده‌های مربوط به کاربر نیز پاک شده‌اند." }, "deletedUserId": { - "message": "Deleted user $ID$ - an owner / admin deleted the user account", + "message": "کاربر $ID$ حذف شد - یک مالک یا مدیر حساب کاربری را حذف کرده است", "placeholders": { "id": { "content": "$1", @@ -10144,7 +10159,7 @@ } }, "userLeftOrganization": { - "message": "User $ID$ left organization", + "message": "کاربر $ID$ سازمان را ترک کرد", "placeholders": { "id": { "content": "$1", @@ -10153,7 +10168,7 @@ } }, "suspendedOrganizationTitle": { - "message": "The $ORGANIZATION$ is suspended", + "message": "سازمان $ORGANIZATION$ تعلیق شده است", "placeholders": { "organization": { "content": "$1", @@ -10162,37 +10177,37 @@ } }, "suspendedUserOrgMessage": { - "message": "Contact your organization owner for assistance." + "message": "برای دریافت کمک با مالک سازمان خود تماس بگیرید." }, "suspendedOwnerOrgMessage": { - "message": "To regain access to your organization, add a payment method." + "message": "برای بازیابی دسترسی به سازمان خود، یک روش پرداخت اضافه کنید." }, "deleteMembers": { - "message": "Delete members" + "message": "حذف اعضا" }, "noSelectedMembersApplicable": { - "message": "This action is not applicable to any of the selected members." + "message": "این عمل برای هیچ یک از اعضا انتخاب شده قابل اجرا نیست." }, "deletedSuccessfully": { - "message": "Deleted successfully" + "message": "با موفقیت حذف شد" }, "freeFamiliesSponsorship": { - "message": "Remove Free Bitwarden Families sponsorship" + "message": "حذف حمایت مالی خانواده‌های رایگان Bitwarden" }, "freeFamiliesSponsorshipPolicyDesc": { - "message": "Do not allow members to redeem a Families plan through this organization." + "message": "اجازه ندهید اعضا از طریق این سازمان طرح خانواده‌ها را دریافت کنند." }, "verifyBankAccountWithStatementDescriptorWarning": { - "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + "message": "پرداخت با حساب بانکی فقط برای مشتریان در ایالات متحده در دسترس است. شما باید حساب بانکی خود را تأیید کنید. ما در ۱ تا ۲ روز کاری آینده یک واریز کوچک انجام خواهیم داد. کد توضیح صورت‌حساب مربوط به این واریز را در صفحه صورتحساب سازمان وارد کنید تا حساب بانکی تأیید شود. عدم تأیید حساب بانکی منجر به پرداخت ناموفق و تعلیق اشتراک شما خواهد شد." }, "verifyBankAccountWithStatementDescriptorInstructions": { - "message": "We have made a micro-deposit to your bank account (this may take 1-2 business days). Enter the six-digit code starting with 'SM' found on the deposit description. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + "message": "ما یک واریز کوچک به حساب بانکی شما انجام داده‌ایم (این ممکن است ۱ تا ۲ روز کاری طول بکشد). کد شش‌رقمی که با 'SM' شروع می‌شود و در توضیحات واریز آمده است را وارد کنید. عدم تأیید حساب بانکی منجر به پرداخت ناموفق و تعلیق اشتراک شما خواهد شد." }, "descriptorCode": { - "message": "Descriptor code" + "message": "کد توضیح دهنده" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "نمی‌توانید مجموعه‌هایی را که فقط دسترسی مشاهده دارند حذف کنید: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -10201,37 +10216,37 @@ } }, "removeMembers": { - "message": "Remove members" + "message": "حذف اعضا" }, "devices": { - "message": "Devices" + "message": "دستگاه‌ها" }, "deviceListDescription": { - "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + "message": "حساب شما در هر یک از دستگاه‌های زیر وارد شده است. اگر دستگاهی را نمی‌شناسید، هم‌اکنون آن را حذف کنید." }, "deviceListDescriptionTemp": { - "message": "Your account was logged in to each of the devices below." + "message": "حساب شما در هر یک از دستگاه‌های زیر وارد شده است." }, "claimedDomains": { - "message": "Claimed domains" + "message": "دامنه‌های ثبت شده" }, "claimDomain": { - "message": "Claim domain" + "message": "ثبت دامنه" }, "reclaimDomain": { - "message": "Reclaim domain" + "message": "ثبت مجدد دامنه" }, "claimDomainNameInputHint": { - "message": "Example: mydomain.com. Subdomains require separate entries to be claimed." + "message": "مثال:.mydomain.com زیر دامنه‌ها برای ثبت نیاز به ورودی‌های جداگانه دارند." }, "automaticClaimedDomains": { - "message": "Automatic Claimed Domains" + "message": "دامنه‌های ثبت شده خودکار" }, "automaticDomainClaimProcess": { - "message": "Bitwarden will attempt to claim the domain 3 times during the first 72 hours. If the domain can’t be claimed, check the DNS record in your host and manually claim. The domain will be removed from your organization in 7 days if it is not claimed." + "message": "Bitwarden در ۷۲ ساعت اول سه بار تلاش خواهد کرد دامنه را ثبت کند. اگر دامنه ثبت نشد، رکورد DNS در میزبان خود را بررسی کرده و به‌صورت دستی ثبت کنید. اگر دامنه ثبت نشود، پس از ۷ روز از سازمان شما حذف خواهد شد." }, "domainNotClaimed": { - "message": "$DOMAIN$ not claimed. Check your DNS records.", + "message": "دامنه $DOMAIN$ ثبت نشده است. رکوردهای DNS خود را بررسی کنید.", "placeholders": { "DOMAIN": { "content": "$1", @@ -10240,19 +10255,19 @@ } }, "domainStatusClaimed": { - "message": "Claimed" + "message": "ثبت شده" }, "domainStatusUnderVerification": { - "message": "Under verification" + "message": "در حال تأیید" }, "claimedDomainsDesc": { - "message": "Claim a domain to own all member accounts whose email address matches the domain. Members will be able to skip the SSO identifier when logging in. Administrators will also be able to delete member accounts." + "message": "یک دامنه را ثبت کنید تا مالک تمام حساب‌های کاربری اعضایی باشید که آدرس ایمیل آنها با آن دامنه مطابقت دارد. اعضا می‌توانند هنگام ورود، مرحله شناسایی SSO را رد کنند. همچنین مدیران قادر خواهند بود حساب‌های کاربری اعضا را حذف کنند." }, "invalidDomainNameClaimMessage": { - "message": "Input is not a valid format. Format: mydomain.com. Subdomains require separate entries to be claimed." + "message": "ورودی یک قالب معتبر نیست. فرمت: .mydomain.com زیر دامنه ها برای ثبت نیاز به ورودی‌های جداگانه دارند." }, "domainClaimedEvent": { - "message": "$DOMAIN$ claimed", + "message": "$DOMAIN$ ثبت شد", "placeholders": { "DOMAIN": { "content": "$1", @@ -10261,7 +10276,7 @@ } }, "domainNotClaimedEvent": { - "message": "$DOMAIN$ not claimed", + "message": "$DOMAIN$ ثبت نشد", "placeholders": { "DOMAIN": { "content": "$1", @@ -10270,7 +10285,7 @@ } }, "updatedRevokeSponsorshipConfirmationForSentSponsorship": { - "message": "If you remove $EMAIL$, the sponsorship for this Family plan cannot be redeemed. Are you sure you want to continue?", + "message": "اگر $EMAIL$ را حذف کنید، امکان دریافت حمایت مالی برای این طرح خانواده وجود نخواهد داشت. آیا مطمئن هستید که می‌خواهید ادامه دهید؟", "placeholders": { "email": { "content": "$1", @@ -10279,7 +10294,7 @@ } }, "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { - "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end and the saved payment method will be charged $40 + applicable tax on $DATE$. You will not be able to redeem a new sponsorship until $DATE$. Are you sure you want to continue?", + "message": "اگر $EMAIL$ را حذف کنید، حمایت مالی این طرح خانواده پایان می‌یابد و روش پرداخت ذخیره شده در تاریخ $DATE$ مبلغ ۴۰ دلار به‌علاوه مالیات مربوطه را از شما کسر خواهد کرد. تا تاریخ $DATE$ قادر به دریافت حمایت مالی جدید نخواهید بود. آیا مطمئن هستید که می‌خواهید ادامه دهید؟", "placeholders": { "email": { "content": "$1", @@ -10292,108 +10307,108 @@ } }, "domainClaimed": { - "message": "Domain claimed" + "message": "دامنه ثبت شده است" }, "organizationNameMaxLength": { - "message": "Organization name cannot exceed 50 characters." + "message": "نام سازمان نمی‌تواند بیش از ۵۰ کاراکتر باشد." }, "rotationCompletedTitle": { - "message": "Key rotation successful" + "message": "چرخش کلید با موفقیت انجام شد" }, "rotationCompletedDesc": { - "message": "Your master password and encryption keys have been updated. Your other devices have been logged out." + "message": "کلمه عبور اصلی و کلیدهای رمزگذاری شما به‌روزرسانی شده‌اند. سایر دستگاه‌های شما از حساب کاربری خارج شده‌اند." }, "trustUserEmergencyAccess": { - "message": "Trust and confirm user" + "message": "اعتماد و تأیید کاربر" }, "trustOrganization": { - "message": "Trust organization" + "message": "اعتماد به سازمان" }, "trust": { - "message": "Trust" + "message": "اعتماد" }, "doNotTrust": { - "message": "Do not trust" + "message": "اعتماد نکنید" }, "organizationNotTrusted": { - "message": "Organization is not trusted" + "message": "سازمان مورد اعتماد نیست" }, "emergencyAccessTrustWarning": { - "message": "For the security of your account, only confirm if you have granted emergency access to this user and their fingerprint matches what is displayed in their account" + "message": "برای امنیت حساب کاربری شما، فقط در صورتی تأیید کنید که دسترسی اضطراری به این کاربر داده‌اید و اثر انگشت او با آنچه در حسابش نمایش داده شده، مطابقت دارد" }, "orgTrustWarning": { - "message": "For the security of your account, only proceed if you are a member of this organization, have account recovery enabled, and the fingerprint displayed below matches the organization's fingerprint." + "message": "برای امنیت حساب کاربری شما، فقط در صورتی ادامه دهید که عضو این سازمان باشید، بازیابی حساب کاربری را فعال کرده باشید و اثر انگشت نمایش داده شده در زیر با اثر انگشت سازمان مطابقت داشته باشد." }, "orgTrustWarning1": { - "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + "message": "این سازمان دارای سیاست سازمانی است که شما را در بازیابی حساب کاربری ثبت‌نام می‌کند. ثبت‌نام به مدیران سازمان اجازه می‌دهد کلمه عبور شما را تغییر دهند. فقط در صورتی ادامه دهید که این سازمان را می‌شناسید و عبارت اثر انگشت نمایش داده شده در زیر با اثر انگشت سازمان مطابقت دارد." }, "trustUser": { - "message": "Trust user" + "message": "به کاربر اعتماد کنید" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "کلمه عبور وارد شده اشتباه است." }, "importSshKey": { - "message": "Import" + "message": "درون ریزی" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "تأیید کلمه عبور" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "کلمه عبور کلید SSH را وارد کنید." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "کلمه عبور را وارد کنید" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "کلید SSH نامعتبر است" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "نوع کلید SSH پشتیبانی نمی‌شود" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "وارد کردن کلید از حافظه موقت" }, "sshKeyImported": { - "message": "SSH key imported successfully" + "message": "کلید SSH با موفقیت وارد شد" }, "copySSHPrivateKey": { - "message": "Copy private key" + "message": "کپی کلید خصوصی" }, "openingExtension": { - "message": "Opening the Bitwarden browser extension" + "message": "باز کردن افزونه مرورگر Bitwarden" }, "somethingWentWrong": { - "message": "Something went wrong..." + "message": "مشکلی پیش آمد..." }, "openingExtensionError": { - "message": "We had trouble opening the Bitwarden browser extension. Click the button to open it now." + "message": "در باز کردن افزونه مرورگر Bitwarden مشکلی پیش آمد. برای باز کردن آن اکنون روی دکمه کلیک کنید." }, "openExtension": { - "message": "Open extension" + "message": "باز کردن افزونه" }, "doNotHaveExtension": { - "message": "Don't have the Bitwarden browser extension?" + "message": "افزونه مرورگر Bitwarden را ندارید؟" }, "installExtension": { - "message": "Install extension" + "message": "نصب افزونه" }, "openedExtension": { - "message": "Opened the browser extension" + "message": "افزونه مرورگر باز شد" }, "openedExtensionViewAtRiskPasswords": { - "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." + "message": "افزونه مرورگر Bitwarden با موفقیت باز شد. اکنون می‌توانید کلمات عبور در معرض خطر خود را بررسی کنید." }, "openExtensionManuallyPart1": { - "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", + "message": "در باز کردن افزونه مرورگر Bitwarden مشکل داشتیم. آیکون Bitwarden را باز کنید", "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" }, "openExtensionManuallyPart2": { - "message": "from the toolbar.", + "message": "از نوار ابزار.", "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" }, "resellerRenewalWarningMsg": { - "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", + "message": "اشتراک شما به‌زودی تمدید می‌شود. برای تضمین ادامه‌ی بدون وقفه‌ی خدمات، قبل از تاریخ $RENEWAL_DATE$ با $RESELLER$ تماس بگیرید و تمدید خود را تأیید کنید.", "placeholders": { "reseller": { "content": "$1", @@ -10406,7 +10421,7 @@ } }, "resellerOpenInvoiceWarningMgs": { - "message": "An invoice for your subscription was issued on $ISSUED_DATE$. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $DUE_DATE$.", + "message": "فاکتوری برای اشتراک شما در تاریخ $ISSUED_DATE$ صادر شده است. برای تضمین ادامه‌ی بدون وقفه‌ی خدمات، قبل از تاریخ $DUE_DATE$ با $RESELLER$ تماس بگیرید و تمدید خود را تأیید کنید.", "placeholders": { "reseller": { "content": "$1", @@ -10423,7 +10438,7 @@ } }, "resellerPastDueWarningMsg": { - "message": "The invoice for your subscription has not been paid. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $GRACE_PERIOD_END$.", + "message": "فاکتور اشتراک شما پرداخت نشده است. برای تضمین ادامه‌ی بدون وقفه‌ی خدمات، قبل از پایان مهلت $GRACE_PERIOD_END$ با $RESELLER$ تماس بگیرید و تمدید خود را تأیید کنید.", "placeholders": { "reseller": { "content": "$1", @@ -10436,13 +10451,13 @@ } }, "restartOrganizationSubscription": { - "message": "Organization subscription restarted" + "message": "اشتراک سازمان مجدداً راه‌اندازی شد" }, "restartSubscription": { - "message": "Restart your subscription" + "message": "اشتراک خود را مجدداً راه‌اندازی کنید" }, "suspendedManagedOrgMessage": { - "message": "Contact $PROVIDER$ for assistance.", + "message": "برای دریافت کمک با $PROVIDER$ تماس بگیرید.", "placeholders": { "provider": { "content": "$1", @@ -10451,16 +10466,16 @@ } }, "accountDeprovisioningNotification": { - "message": "Administrators now have the ability to delete member accounts that belong to a claimed domain." + "message": "اکنون مدیران قادر به حذف حساب‌های کاربری اعضایی هستند که به دامنه ثبت شده تعلق دارند." }, "deleteManagedUserWarningDesc": { - "message": "This action will delete the member account including all items in their vault. This replaces the previous Remove action." + "message": "این اقدام حساب کاربری عضو را به همراه تمام موارد داخل گاوصندوق او حذف خواهد کرد. این جایگزین عملیات حذف قبلی است." }, "deleteManagedUserWarning": { - "message": "Delete is a new action!" + "message": "حذف یک اقدام جدید است!" }, "seatsRemaining": { - "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "message": "شما $REMAINING$ صندلی باقی‌مانده از مجموع $TOTAL$ صندلی اختصاص یافته به این سازمان دارید. برای مدیریت اشتراک خود با ارائه‌دهنده‌تان تماس بگیرید.", "placeholders": { "remaining": { "content": "$1", @@ -10473,19 +10488,19 @@ } }, "existingOrganization": { - "message": "Existing organization" + "message": "سازمان موجود" }, "selectOrganizationProviderPortal": { - "message": "Select an organization to add to your Provider Portal." + "message": "یک سازمان برای افزودن به پورتال ارائه‌دهنده خود انتخاب کنید." }, "noOrganizations": { - "message": "There are no organizations to list" + "message": "هیچ سازمانی برای نمایش وجود ندارد" }, "yourProviderSubscriptionCredit": { - "message": "Your provider subscription will receive a credit for any remaining time in the organization's subscription." + "message": "اشتراک ارائه‌دهنده شما برای هر زمان باقی‌مانده در اشتراک سازمان اعتبار دریافت خواهد کرد." }, "doYouWantToAddThisOrg": { - "message": "Do you want to add this organization to $PROVIDER$?", + "message": "آیا می‌خواهید این سازمان را به $PROVIDER$ اضافه کنید؟", "placeholders": { "provider": { "content": "$1", @@ -10494,13 +10509,13 @@ } }, "addedExistingOrganization": { - "message": "Added existing organization" + "message": "سازمان موجود اضافه شد" }, "assignedExceedsAvailable": { - "message": "Assigned seats exceed available seats." + "message": "تعداد صندلی‌های اختصاص داده شده بیشتر از صندلی‌های موجود است." }, "userkeyRotationDisclaimerEmergencyAccessText": { - "message": "Fingerprint phrase for $NUM_USERS$ contacts for which you have enabled emergency access.", + "message": "عبارت اثرانگشت برای $NUM_USERS$ مخاطبی که دسترسی اضطراری به آن‌ها فعال کرده‌اید.", "placeholders": { "num_users": { "content": "$1", @@ -10509,7 +10524,7 @@ } }, "userkeyRotationDisclaimerAccountRecoveryOrgsText": { - "message": "Fingerprint phrase for the organization $ORG_NAME$ for which you have enabled account recovery.", + "message": "عبارت اثرانگشت برای سازمان $ORG_NAME$ که بازیابی حساب کاربری را برای آن فعال کرده‌اید.", "placeholders": { "org_name": { "content": "$1", @@ -10518,97 +10533,122 @@ } }, "userkeyRotationDisclaimerDescription": { - "message": "Rotating your encryption keys will require you to trust keys of any organizations that can recover your account, and any contacts that you have enabled emergency access for. To continue, make sure you can verify the following:" + "message": "تغییر کلیدهای رمزنگاری شما نیازمند اعتماد به کلیدهای هر سازمانی است که می‌تواند حساب کاربری شما را بازیابی کند، و همچنین هر مخاطبی که دسترسی اضطراری برای او فعال کرده‌اید. برای ادامه، مطمئن شوید که می‌توانید موارد زیر را تأیید کنید:" }, "userkeyRotationDisclaimerTitle": { - "message": "Untrusted encryption keys" + "message": "کلیدهای رمزنگاری غیرقابل اعتماد" }, "changeAtRiskPassword": { - "message": "Change at-risk password" + "message": "تغییر کلمه عبور در معرض خطر" }, "removeUnlockWithPinPolicyTitle": { - "message": "Remove Unlock with PIN" + "message": "حذف گزینه‌ی بازکردن با کد پین" }, "removeUnlockWithPinPolicyDesc": { - "message": "Do not allow members to unlock their account with a PIN." + "message": "به اعضا اجازه ندهید با کد پین حساب کاربری خود را باز کنند." }, "upgradeForFullEventsMessage": { - "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." + "message": "گزارش‌های رویداد برای سازمان شما ذخیره نمی‌شوند. برای دسترسی کامل به گزارش‌های رویداد سازمان، به طرح تیم‌ها یا سازمان‌های بزرگ ارتقا دهید." }, "upgradeEventLogTitleMessage": { - "message": "Upgrade to see event logs from your organization." + "message": "برای مشاهده گزارش‌های رویدادهای سازمان خود، ارتقا دهید." }, "upgradeEventLogMessage": { - "message": "These events are examples only and do not reflect real events within your Bitwarden organization." + "message": "این رویدادها تنها مثال هستند و رویدادهای واقعی درون سازمان Bitwarden شما را نشان نمی‌دهند." }, "cannotCreateCollection": { - "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections." + "message": "سازمان‌های رایگان می‌توانند حداکثر تا ۲ مجموعه داشته باشند. برای اضافه کردن مجموعه‌های بیشتر، به طرح پولی ارتقا دهید." }, "businessUnit": { - "message": "Business Unit" + "message": "واحد کسب و کار" }, "businessUnits": { - "message": "Business Units" + "message": "واحدهای کسب و کار" }, "newBusinessUnit": { - "message": "New business unit" + "message": "واحد کسب و کار جدید" + }, + "sendsTitleNoItems": { + "message": "اطلاعات حساس را به‌صورت ایمن ارسال کنید", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "پرونده‌ها و داده‌های خود را به‌صورت امن با هر کسی، در هر پلتفرمی به اشتراک بگذارید. اطلاعات شما در حین اشتراک‌گذاری به‌طور کامل رمزگذاری انتها به انتها باقی خواهد ماند و میزان افشا محدود می‌شود.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "ساخت سریع کلمات عبور" + }, + "generatorNudgeBodyOne": { + "message": "به‌راحتی کلمات عبور قوی و منحصر به فرد ایجاد کنید با کلیک روی", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "برای کمک به حفظ امنیت ورودهای شما.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "با کلیک روی دکمه تولید رمز عبور، به‌راحتی کلمات عبور قوی و منحصر به‌ فرد ایجاد کنید تا ورودهای شما ایمن باقی بمانند.", + "description": "Aria label for the body content of the generator nudge" }, "newLoginNudgeTitle": { - "message": "Save time with autofill" + "message": "با پر کردن خودکار در وقت خود صرفه جویی کنید" }, "newLoginNudgeBodyOne": { - "message": "Include a", + "message": "شامل یک", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "وب‌سایت", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyTwo": { - "message": "so this login appears as an autofill suggestion.", + "message": "تا این ورود به عنوان پیشنهاد پر کردن خودکار ظاهر شود.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, "newCardNudgeTitle": { - "message": "Seamless online checkout" + "message": "پرداخت آنلاین بدون وقفه" }, "newCardNudgeBody": { - "message": "With cards, easily autofill payment forms securely and accurately." + "message": "با کارت‌ها، فرم‌های پرداخت را به‌راحتی و با امنیت و دقت پر کنید." }, "newIdentityNudgeTitle": { - "message": "Simplify creating accounts" + "message": "ساخت حساب‌های کاربری را ساده کنید" }, "newIdentityNudgeBody": { - "message": "With identities, quickly autofill long registration or contact forms." + "message": "با هویت‌ها، به سرعت فرم‌های طولانی ثبت‌نام یا تماس را پر کنید." }, "newNoteNudgeTitle": { - "message": "Keep your sensitive data safe" + "message": "اطلاعات حساس خود را ایمن نگه دارید" }, "newNoteNudgeBody": { - "message": "With notes, securely store sensitive data like banking or insurance details." + "message": "با یادداشت‌ها، اطلاعات حساسی مانند جزئیات بانکی یا بیمه را به‌صورت ایمن ذخیره کنید." }, "newSshNudgeTitle": { - "message": "Developer-friendly SSH access" + "message": "دسترسی SSH مناسب برای توسعه‌دهندگان" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "کلیدهای خود را ذخیره کنید و با عامل SSH برای احراز هویت سریع و رمزگذاری‌شده متصل شوید.", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Learn more about SSH agent", + "message": "اطلاعات بیشتر درباره عامل SSH را بیاموزید", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "restart": { - "message": "Restart" + "message": "راه اندازی مجدد" }, "verifyProviderBankAccountWithStatementDescriptorWarning": { - "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." + "message": "پرداخت با حساب بانکی تنها برای مشتریان در ایالات متحده در دسترس است. شما باید حساب بانکی خود را تأیید کنید. ما ظرف ۱ تا ۲ روز کاری آینده یک واریز کوچک انجام خواهیم داد. کد توضیح صورت‌حساب این واریز را در صفحه اشتراک ارائه‌دهنده وارد کنید تا حساب بانکی تأیید شود. عدم تأیید حساب بانکی منجر به عدم پرداخت و تعلیق اشتراک شما خواهد شد." }, "clickPayWithPayPal": { - "message": "Please click the Pay with PayPal button to add your payment method." + "message": "لطفاً برای افزودن روش پرداخت خود، روی دکمه پرداخت با پی‌پال کلیک کنید." } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index ccbcf4f7cc3..04504a49c2e 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -547,12 +547,6 @@ "message": "Laajenna tai supista", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Luo salasana" - }, - "generatePassphrase": { - "message": "Luo salalause" - }, "checkPassword": { "message": "Tarkasta, onko salasana paljastunut." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Luo sähköpostiosoite" }, + "generatePassword": { + "message": "Luo salasana" + }, + "generatePassphrase": { + "message": "Luo salalause" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Arvon tulee olla väliltä $MIN$—$MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Käytä tätä salasanaa" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Käytä tätä käyttäjätunnusta" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 97e22e18045..8aeb3d17aa1 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -547,12 +547,6 @@ "message": "Palakihin/paliitin", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Gumawa ng password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Tingnan kung nakompromiso na ba ang password." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Gumawa ng password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index d881ecfefd3..e8016b547f9 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -547,12 +547,6 @@ "message": "Déplier / replier", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Générer un mot de passe" - }, - "generatePassphrase": { - "message": "Générer une phrase de passe" - }, "checkPassword": { "message": "Vérifier si le mot de passe a été exposé." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Générer un courriel" }, + "generatePassword": { + "message": "Générer un mot de passe" + }, + "generatePassphrase": { + "message": "Générer une phrase de passe" + }, + "passwordGenerated": { + "message": "Mot de passe généré" + }, + "passphraseGenerated": { + "message": "Phrase de passe générée" + }, + "usernameGenerated": { + "message": "Nom d'utilisateur généré" + }, + "emailGenerated": { + "message": "Courriel généré" + }, "spinboxBoundariesHint": { "message": "La valeur doit être comprise entre $MIN$ et $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Utiliser ce mot de passe" }, + "useThisPassphrase": { + "message": "Utiliser cette phrase de passe" + }, "useThisUsername": { "message": "Utiliser ce nom d'utilisateur" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "Nouvelle unité d'affaires" }, + "sendsTitleNoItems": { + "message": "Envoyez des informations sensibles, en toute sécurité", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Partagez des fichiers et des données en toute sécurité avec n'importe qui, sur n'importe quelle plateforme. Vos informations resteront chiffrées de bout en bout tout en limitant l'exposition.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Créer rapidement des mots de passe" + }, + "generatorNudgeBodyOne": { + "message": "Créez facilement des mots de passe forts et uniques en cliquant sur", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "pour vous aider à garder vos identifiants sécuritaires.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Créez facilement des mots de passe forts et uniques en cliquant sur le bouton Générer un mot de passe pour vous aider à garder vos identifiants sécuritaires.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Gagnez du temps avec le remplissage automatique" }, diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 707f9752ab6..004090b4fab 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 6dd2d9243b1..2f11b460d66 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -547,12 +547,6 @@ "message": "שנה מצב כיווץ", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "צור סיסמה" - }, - "generatePassphrase": { - "message": "צור ביטוי סיסמה" - }, "checkPassword": { "message": "בדוק אם הסיסמה נחשפה." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "צור דוא\"ל" }, + "generatePassword": { + "message": "צור סיסמה" + }, + "generatePassphrase": { + "message": "צור ביטוי סיסמה" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "הערך חייב להיות בין $MIN$ ל־$MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "השתמש בסיסמה זו" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "השתמש בשם משתמש זה" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 22a31e5df70..4fbcada7060 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 3c37dc9cedf..501741f494a 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -547,12 +547,6 @@ "message": "Sažmi/Proširi", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generiraj lozinku" - }, - "generatePassphrase": { - "message": "Generiraj fraznu lozinku" - }, "checkPassword": { "message": "Provjeri je li lozinka bila ukradena." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generiraj e-poštu" }, + "generatePassword": { + "message": "Generiraj lozinku" + }, + "generatePassphrase": { + "message": "Generiraj fraznu lozinku" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Vrijednost mora biti u rasponu $MIN$ - $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Koristi ovu lozinku" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Koristi ovo korisničko ime" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Uštedi vrijeme s auto-ispunom" }, diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 33ea6c3d9cd..42da598459e 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -547,12 +547,6 @@ "message": "Összecsukás/kinyitás", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Jelszó generálása" - }, - "generatePassphrase": { - "message": "Jelmondat generálás" - }, "checkPassword": { "message": "A jelszóvédelmi állapot ellenőrzése." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Email generálása" }, + "generatePassword": { + "message": "Jelszó generálása" + }, + "generatePassphrase": { + "message": "Jelmondat generálás" + }, + "passwordGenerated": { + "message": "A jelszó generálásra került." + }, + "passphraseGenerated": { + "message": "A jelmondat generálásra került." + }, + "usernameGenerated": { + "message": "A felhasználónév generálásra került." + }, + "emailGenerated": { + "message": "Az email generálásra került." + }, "spinboxBoundariesHint": { "message": "Az érték legyen $MIN$ és $MAX$ között.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Jelszó használata" }, + "useThisPassphrase": { + "message": "Jelmondat használata" + }, "useThisUsername": { "message": "Felhasználónév használata" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "Új üzleti egység" }, + "sendsTitleNoItems": { + "message": "Érzékeny információt küldése biztonságosan", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Fájlok vagy adatok megosztása biztonságosan bárkivel, bármilyen platformon. Az információk titkosítva maradnak a végpontokon, korlátozva a kitettséget.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Jelszavak gyors létrehozása" + }, + "generatorNudgeBodyOne": { + "message": "Könnyen létrehozhatunk erős és egyedi jelszavakat a gombra kattintva", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Könnyedén hozhatunk létre erős és egyedi jelszavakat a Jelszó generálása gombra kattintva, amely segít megőrizni a bejelentkezések biztonságát.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 379a6424333..24f095ed094 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -547,12 +547,6 @@ "message": "Alihkan Ciutkan", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Buat Kata Sandi" - }, - "generatePassphrase": { - "message": "Buat frasa sandi" - }, "checkPassword": { "message": "Periksa apakah kata sandi telah terekspos." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Buat Kata Sandi" + }, + "generatePassphrase": { + "message": "Buat frasa sandi" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 8019ac7a750..d1e7f59d3d9 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -547,12 +547,6 @@ "message": "Comprimi/espandi", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Genera password" - }, - "generatePassphrase": { - "message": "Genera passphrase" - }, "checkPassword": { "message": "Verifica se la password è stata esposta." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Genera email" }, + "generatePassword": { + "message": "Genera password" + }, + "generatePassphrase": { + "message": "Genera passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Il valore deve essere compreso tra $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Usa questa password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Usa questo nome utente" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 2de545cd27a..bc692f4cd78 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -547,12 +547,6 @@ "message": "開く/閉じる", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "パスワードの自動生成" - }, - "generatePassphrase": { - "message": "パスフレーズを生成" - }, "checkPassword": { "message": "パスワードが漏洩していないか確認する" }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "メールアドレスを生成" }, + "generatePassword": { + "message": "パスワードの自動生成" + }, + "generatePassphrase": { + "message": "パスフレーズを生成" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "値は $MIN$ から $MAX$ の間でなければなりません。", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "このパスワードを使用する" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "このユーザー名を使用する" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 863f8d11dea..3f6fe55ffe0 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -547,12 +547,6 @@ "message": "ჩამოშლის გადამრთველი", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "წარმოქმენი პაროლი" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "შეამოწმე პაროლი თუ გაბაზრდა" }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "წარმოქმენი პაროლი" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 27a160a9a3c..09873860bce 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 98c317d377f..518fe2090df 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -547,12 +547,6 @@ "message": "ಟಾಗಲ್ ಕುಸಿತ", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "ಪಾಸ್ವರ್ಡ್ ರಚಿಸಿ" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "ಪಾಸ್ವರ್ಡ್ ಬಹಿರಂಗಗೊಂಡಿದೆಯೇ ಎಂದು ಪರಿಶೀಲಿಸಿ." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "ಪಾಸ್ವರ್ಡ್ ರಚಿಸಿ" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 2340ae849fa..991ae2e31b8 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -547,12 +547,6 @@ "message": "Toggle Collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "비밀번호 생성" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "비밀번호가 노출되었는지 확인합니다." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "비밀번호 생성" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 27818e4e3b9..5535e23b030 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -547,12 +547,6 @@ "message": "Pārslēgt sakļaušanu", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Veidot paroli" - }, - "generatePassphrase": { - "message": "Izveidot paroles vārdkopu" - }, "checkPassword": { "message": "Pārbaudīt, vai parole ir bijusi nopludināta." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Izveidot e-pasta adresi" }, + "generatePassword": { + "message": "Veidot paroli" + }, + "generatePassphrase": { + "message": "Izveidot paroles vārdkopu" + }, + "passwordGenerated": { + "message": "Parole izveidota" + }, + "passphraseGenerated": { + "message": "Paroles vārdkopa izveidota" + }, + "usernameGenerated": { + "message": "Lietotājvārds izveidots" + }, + "emailGenerated": { + "message": "E-pasta adrese izveidota" + }, "spinboxBoundariesHint": { "message": "Vērtībai jābūt starp $MIN$ un $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Izmantot šo paroli" }, + "useThisPassphrase": { + "message": "Izmantot šo paroles vārdkopu" + }, "useThisUsername": { "message": "Izmantot šo lietotājvārdu" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "Jauna uzņēmējdarbības vienība" }, + "sendsTitleNoItems": { + "message": "Drošā veidā nosūti jūtīgu informāciju", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Kopīgo datnes un datus drošā veidā ar ikvienu jebkurā platformā! Tava informācija paliks pilnībā šifrēta, vienlaikus ierobežojot riskantumu.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Ātra paroļu izveidošana" + }, + "generatorNudgeBodyOne": { + "message": "Vienkārša spēcīgu un neatkārtojamu paroļu izveidošana ar", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": ", lai palīdzētu uzturērt pieteikšanās vienumus drošus.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Vienkārša spēcīgu un neatkārtojamu paroļu izveidošana ar pogu \"Izveidot paroli\", lai palīdzētu uzturēt pieteikšanās vienumus drošus.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Laika ietaupīšana ar automātisko aizpildi" }, diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 54a837b83e1..01c1f6307ee 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -547,12 +547,6 @@ "message": "ചുരുക്കുക", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "പാസ്‌വേഡ് സൃഷ്ടിക്കുക" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "പാസ്സ്‌വേർഡ് ചോർന്നോ എന്ന് നോക്കുക." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "പാസ്‌വേഡ് സൃഷ്ടിക്കുക" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 27a160a9a3c..60ddec4332e 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -1,24 +1,24 @@ { "allApplications": { - "message": "All applications" + "message": "सर्व अ‍ॅप्लिकेशन्स" }, "appLogoLabel": { - "message": "Bitwarden logo" + "message": "बिटवॉर्डन चिन्ह" }, "criticalApplications": { - "message": "Critical applications" + "message": "महत्त्वाचे अ‍ॅप्लिकेशन्स" }, "noCriticalAppsAtRisk": { - "message": "No critical applications at risk" + "message": "कोणतेही महत्त्वाचे अ‍ॅप्लिकेशन्स धोक्यात नाहीत" }, "accessIntelligence": { - "message": "Access Intelligence" + "message": "अ‍ॅक्सेस इंटेलिजेंस" }, "riskInsights": { - "message": "Risk Insights" + "message": "जोखीम अंतर्दृष्टी" }, "passwordRisk": { - "message": "Password Risk" + "message": "पासवर्डचा धोका" }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." @@ -54,7 +54,7 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "नवीन लॉगिन आयटम तयार करा" }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -96,7 +96,7 @@ "message": "Mark critical apps" }, "markAppAsCritical": { - "message": "Mark app as critical" + "message": "अ‍ॅपला गंभीर म्हणून चिन्हांकित करा" }, "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" @@ -105,13 +105,13 @@ "message": "Application" }, "atRiskPasswords": { - "message": "At-risk passwords" + "message": "धोकादायक पासवर्ड" }, "requestPasswordChange": { - "message": "Request password change" + "message": "पासवर्ड बदलण्याची विनंती करा" }, "totalPasswords": { - "message": "Total passwords" + "message": "एकूण पासवर्ड" }, "searchApps": { "message": "Search applications" @@ -120,7 +120,7 @@ "message": "At-risk members" }, "atRiskMembersWithCount": { - "message": "At-risk members ($COUNT$)", + "message": "जोखीम गटातील सदस्य ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -186,7 +186,7 @@ "message": "What type of item is this?" }, "name": { - "message": "Name" + "message": "नाव" }, "uri": { "message": "URI" @@ -205,13 +205,13 @@ "message": "New URI" }, "username": { - "message": "Username" + "message": "वापरकर्तानाव" }, "password": { - "message": "Password" + "message": "पासवर्ड" }, "newPassword": { - "message": "New password" + "message": "नवीन पासवर्ड" }, "passphrase": { "message": "Passphrase" @@ -232,7 +232,7 @@ "message": "Cardholder name" }, "loginCredentials": { - "message": "Login credentials" + "message": "लॉगिन क्रेडेन्शियल्स" }, "personalDetails": { "message": "Personal details" @@ -241,13 +241,13 @@ "message": "Identification" }, "contactInfo": { - "message": "Contact info" + "message": "संपर्क माहिती" }, "cardDetails": { - "message": "Card details" + "message": "कार्ड तपशील" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "$BRAND$ ची माहिती", "placeholders": { "brand": { "content": "$1", @@ -259,16 +259,16 @@ "message": "Item history" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "ऑथेंटिकेटर की" }, "autofillOptions": { - "message": "Autofill options" + "message": "ऑटोफिल पर्याय" }, "websiteUri": { - "message": "Website (URI)" + "message": "वेबसाइट (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "वेबसाइट (URI) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -318,25 +318,25 @@ "message": "Autofill on page load?" }, "number": { - "message": "Number" + "message": "क्रमांक" }, "brand": { - "message": "Brand" + "message": "ब्रँड" }, "expiration": { - "message": "Expiration" + "message": "कालबाह्यता" }, "securityCode": { - "message": "Security code (CVV)" + "message": "सुरक्षा कोड (CVV)" }, "securityCodeSlashCVV": { - "message": "Security code / CVV" + "message": "सुरक्षा कोड / CVV" }, "identityName": { "message": "Identity name" }, "company": { - "message": "Company" + "message": "कंपनी" }, "ssn": { "message": "Social Security number" @@ -348,46 +348,46 @@ "message": "License number" }, "email": { - "message": "Email" + "message": "ईमेल" }, "phone": { "message": "Phone" }, "january": { - "message": "January" + "message": "जानेवरी" }, "february": { - "message": "February" + "message": "फेब्रुवरी" }, "march": { - "message": "March" + "message": "मार्च" }, "april": { - "message": "April" + "message": "एप्रिल" }, "may": { - "message": "May" + "message": "मे" }, "june": { - "message": "June" + "message": "जून" }, "july": { - "message": "July" + "message": "जुलै" }, "august": { - "message": "August" + "message": "ऑगस्ट" }, "september": { - "message": "September" + "message": "सेप्टेंबर" }, "october": { - "message": "October" + "message": "ऑक्टोबर" }, "november": { - "message": "November" + "message": "नोवेंबर" }, "december": { - "message": "December" + "message": "डिसेंबर" }, "title": { "message": "Title" @@ -414,13 +414,13 @@ "message": "If you've renewed it, update the card's information" }, "expirationMonth": { - "message": "Expiration month" + "message": "कालबाह्यता महिना" }, "expirationYear": { - "message": "Expiration year" + "message": "कालबाह्यता वर्ष" }, "authenticatorKeyTotp": { - "message": "Authenticator key (TOTP)" + "message": "ऑथेंटिकेटर की (TOTP)" }, "totpHelperTitle": { "message": "Make 2-step verification seamless" @@ -547,14 +547,8 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { - "message": "Check if password has been exposed." + "message": "पासवर्ड उघड झाला आहे का तपासा." }, "passwordExposed": { "message": "This password has been exposed $VALUE$ time(s) in data breaches. You should change it.", @@ -566,13 +560,13 @@ } }, "passwordSafe": { - "message": "This password was not found in any known data breaches. It should be safe to use." + "message": "हा पासवर्ड कोणत्याही c डेटा उल्लंघनात आढळला नाही. तो वापरण्यास सुरक्षित असावा." }, "save": { - "message": "Save" + "message": "जतन करा" }, "cancel": { - "message": "Cancel" + "message": "रद्द करा" }, "canceled": { "message": "Canceled" @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 27a160a9a3c..09873860bce 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index b2068d1d939..360e59ea70f 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -547,12 +547,6 @@ "message": "Bytt mellom skjul/utvid", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generer et passord" - }, - "generatePassphrase": { - "message": "Generér passordfrase" - }, "checkPassword": { "message": "Sjekk om passordet har blitt utsatt." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generér E-post" }, + "generatePassword": { + "message": "Generer et passord" + }, + "generatePassphrase": { + "message": "Generér passordfrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Verdien må være mellom $MIN$ og $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Bruk dette passordet" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Bruk dette brukernavnet" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Spar tid med auto-utfylling" }, diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index c8c0c90dcde..3dc7ab5b476 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index d92b9707fc9..fdb5e68d3b4 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -547,12 +547,6 @@ "message": "Inklappen/uitklappen", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Wachtwoord genereren" - }, - "generatePassphrase": { - "message": "Wachtwoordzin genereren" - }, "checkPassword": { "message": "Controleer of wachtwoord is gelekt." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "E-mailadres genereren" }, + "generatePassword": { + "message": "Wachtwoord genereren" + }, + "generatePassphrase": { + "message": "Wachtwoordzin genereren" + }, + "passwordGenerated": { + "message": "Wachtwoord gegenereerd" + }, + "passphraseGenerated": { + "message": "Wachtwoordzin gegenereerd" + }, + "usernameGenerated": { + "message": "Gebruikersnaam gegenereerd" + }, + "emailGenerated": { + "message": "E-mail gegenereerd" + }, "spinboxBoundariesHint": { "message": "Waarde moet tussen $MIN$ en $MAX$ liggen.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Dit wachtwoord gebruiken" }, + "useThisPassphrase": { + "message": "Deze wachtwoordzin gebruiken" + }, "useThisUsername": { "message": "Deze gebruikersnaam gebruiken" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "Nieuwe bedrijfseenheid" }, + "sendsTitleNoItems": { + "message": "Gevoelige informatie veilig versturen", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Deel bestanden en gegevens veilig met iedereen, op elk platform. Je informatie blijft end-to-end versleuteld terwijl en blootstelling beperkt.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Snel wachtwoorden maken" + }, + "generatorNudgeBodyOne": { + "message": "Maak eenvoudig sterke en unieke wachtwoorden door te klikken op", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "om je te helpen je inloggegevens veilig te houden.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Maak eenvoudig sterke en unieke wachtwoorden door op de knop Wachtwoord genereren te klikken om je logins veilig te houden.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Tijd besparen met automatisch aanvullen" }, diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 619c1776cb6..12c41a6e3db 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -547,12 +547,6 @@ "message": "Gøym/vid ut", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Laga passord" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Laga passord" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 27a160a9a3c..09873860bce 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index de9caf9bbce..7480135bc41 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -547,12 +547,6 @@ "message": "Zwiń/rozwiń", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Wygeneruj hasło" - }, - "generatePassphrase": { - "message": "Wygeneruj hasło wyrazowe" - }, "checkPassword": { "message": "Sprawdź, czy hasło zostało ujawnione." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Wygeneruj e-mail" }, + "generatePassword": { + "message": "Wygeneruj hasło" + }, + "generatePassphrase": { + "message": "Wygeneruj hasło wyrazowe" + }, + "passwordGenerated": { + "message": "Hasło zostało wygenerowane" + }, + "passphraseGenerated": { + "message": "Hasło wyrazowe zostało wygenerowane" + }, + "usernameGenerated": { + "message": "Nazwa użytkownika została wygenerowana" + }, + "emailGenerated": { + "message": "E-mail został wygenerowany" + }, "spinboxBoundariesHint": { "message": "Wartość musi być pomiędzy $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Użyj tego hasła" }, + "useThisPassphrase": { + "message": "Użyj tego hasła wyrazowego" + }, "useThisUsername": { "message": "Użyj tej nazwy użytkownika" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "Nowa jednostka biznesowa" }, + "sendsTitleNoItems": { + "message": "Wysyłaj bezpiecznie poufne informacje", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Udostępniaj pliki i dane bezpiecznie każdemu, na każdej platformie. Twoje dane pozostaną zaszyfrowane end-to-end przy jednoczesnym ograniczeniu narażenia.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Szybko twórz hasła" + }, + "generatorNudgeBodyOne": { + "message": "Łatwo twórz silne i unikalne hasła, klikając na", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": ", aby pomóc Ci zachować bezpieczeństwo Twoich danych logowania.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Łatwo twórz silne i unikalne hasła, klikając na Wygeneruj Hasło, aby pomóc Ci zachować bezpieczeństwo Twoich danych logowania.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Oszczędzaj czas dzięki autouzupełnianiu" }, diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 04403ade2d9..32d060465a5 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -547,12 +547,6 @@ "message": "Alternar Colapso", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Gerar Senha" - }, - "generatePassphrase": { - "message": "Gerar frase secreta" - }, "checkPassword": { "message": "Verifique se a senha foi exposta." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Gerar e-mail" }, + "generatePassword": { + "message": "Gerar Senha" + }, + "generatePassphrase": { + "message": "Gerar frase secreta" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Valor deve ser entre $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use esta senha" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use este nome de usuário" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "Nova unidade de negócio" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index dc66923f629..9aa737e6b23 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -547,12 +547,6 @@ "message": "Alternar colapso", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Gerar palavra-passe" - }, - "generatePassphrase": { - "message": "Gerar frase de acesso" - }, "checkPassword": { "message": "Verificar se a palavra-passe foi exposta." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Gerar e-mail" }, + "generatePassword": { + "message": "Gerar palavra-passe" + }, + "generatePassphrase": { + "message": "Gerar frase de acesso" + }, + "passwordGenerated": { + "message": "Palavra-passe gerada" + }, + "passphraseGenerated": { + "message": "Frase de acesso gerada" + }, + "usernameGenerated": { + "message": "Nome de utilizador gerado" + }, + "emailGenerated": { + "message": "E-mail gerado" + }, "spinboxBoundariesHint": { "message": "O valor deve estar entre $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Utilizar esta palavra-passe" }, + "useThisPassphrase": { + "message": "Utilizar esta frase de acesso" + }, "useThisUsername": { "message": "Utilizar este nome de utilizador" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "Nova unidade de negócio" }, + "sendsTitleNoItems": { + "message": "Envie informações sensíveis com segurança", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Partilhe ficheiros e dados de forma segura com qualquer pessoa, em qualquer plataforma. As suas informações permanecerão encriptadas ponto a ponto, limitando a exposição.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Criar rapidamente palavras-passe" + }, + "generatorNudgeBodyOne": { + "message": "Crie facilmente palavras-passe fortes e únicas clicando em", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "para o ajudar a manter as suas credenciais seguras.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Crie facilmente palavras-passe fortes e únicas clicando no botão Gerar palavra-passe para o ajudar a manter as suas credenciais seguras.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Poupe tempo com o preenchimento automático" }, diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 632733642b1..8ea595a4a32 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -547,12 +547,6 @@ "message": "Comutare restrângere", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generare parolă" - }, - "generatePassphrase": { - "message": "Generare parolă" - }, "checkPassword": { "message": "Verificați dacă parola a fost dezvăluită." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generare parolă" + }, + "generatePassphrase": { + "message": "Generare parolă" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index c30371f7d86..b353a8d0e61 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -547,12 +547,6 @@ "message": "Свернуть/развернуть", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Сгенерировать пароль" - }, - "generatePassphrase": { - "message": "Создать парольную фразу" - }, "checkPassword": { "message": "Проверьте, не скомпрометирован ли пароль." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Сгенерировать email" }, + "generatePassword": { + "message": "Сгенерировать пароль" + }, + "generatePassphrase": { + "message": "Создать парольную фразу" + }, + "passwordGenerated": { + "message": "Пароль создан" + }, + "passphraseGenerated": { + "message": "Парольная фраза создана" + }, + "usernameGenerated": { + "message": "Имя пользователя создано" + }, + "emailGenerated": { + "message": "Email создан" + }, "spinboxBoundariesHint": { "message": "Значение должно быть между $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Использовать этот пароль" }, + "useThisPassphrase": { + "message": "Использовать эту парольную фразу" + }, "useThisUsername": { "message": "Использовать это имя пользователя" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "Новая бизнес-единица" }, + "sendsTitleNoItems": { + "message": "Безопасная отправка конфиденциальной информации", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Безопасно обменивайтесь файлами и данными с кем угодно на любой платформе. Ваша информация надежно шифруется и доступ к ней ограничен.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Быстрое создание паролей" + }, + "generatorNudgeBodyOne": { + "message": "Легко создавайте надежные и уникальные пароли, нажатием на кнопку,", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "чтобы обеспечить безопасность ваших логинов.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Легко создавайте надежные и уникальные пароли, нажатием на кнопку 'Сгенерировать пароль', чтобы обеспечить безопасность ваших логинов.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Экономьте время с помощью автозаполнения" }, diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index f75fd53c209..16542445a85 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index e785bd3749b..127e8cce83e 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -547,12 +547,6 @@ "message": "Prepnúť zloženie", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generovať heslo" - }, - "generatePassphrase": { - "message": "Generovať prístupovú frázu" - }, "checkPassword": { "message": "Overiť či došlo k úniku hesla." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generovať e-mail" }, + "generatePassword": { + "message": "Generovať heslo" + }, + "generatePassphrase": { + "message": "Generovať prístupovú frázu" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Hodnota musí byť medzi $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Použiť toto heslo" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Použiť toto používateľské meno" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "Nová organizačná jednotka" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Ušetrite čas s automatickým vypĺňaním" }, diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 5a43561bf5a..0731d963194 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -547,12 +547,6 @@ "message": "Skrči/Razširi", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generiraj geslo" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Preveri izpostavljenost gesla." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generiraj geslo" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index e54b6d148ca..b698ef5e281 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -547,12 +547,6 @@ "message": "Пребаци проширење", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Генерисање лозинке" - }, - "generatePassphrase": { - "message": "Генеришите приступну фразу" - }, "checkPassword": { "message": "Проверите да ли је лозинка изложена." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Генеришите имејл" }, + "generatePassword": { + "message": "Генерисање лозинке" + }, + "generatePassphrase": { + "message": "Генеришите приступну фразу" + }, + "passwordGenerated": { + "message": "Лозинка генерисана" + }, + "passphraseGenerated": { + "message": "Приступна фраза је генерисана" + }, + "usernameGenerated": { + "message": "Корисничко име генерисано" + }, + "emailGenerated": { + "message": "Имејл генерисан" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Употреби ову лозинку" }, + "useThisPassphrase": { + "message": "Употреби ову приступну фразу" + }, "useThisUsername": { "message": "Употреби ово корисничко име" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Шаљите бзбедно осетљиве информације", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Делите датотеке и податке безбедно са било ким, на било којој платформи. Ваше информације ће остати шифроване од почетка-до-краја уз ограничење изложености.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Брзо креирајте лозинке" + }, + "generatorNudgeBodyOne": { + "message": "Лако креирајте снажне и јединствене лозинке кликом на", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "да вам помогне да задржите своје пријаве сигурно.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Лако креирајте снажне и јединствене лозинке кликом на дугме „Генерирате лозинку“ да вам помогне да чувате своје пријаве на сигурно.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Уштедите време са ауто-пуњењем" }, diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 95b9348aa04..34282b8aaeb 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generiši lozinku" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generiši lozinku" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 3bbfc4577ea..43658ca8129 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -547,12 +547,6 @@ "message": "Växla synlig/dold", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generera lösenord" - }, - "generatePassphrase": { - "message": "Generera lösenfras" - }, "checkPassword": { "message": "Kontrollera om ditt lösenord har äventyrats." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generera lösenord" + }, + "generatePassphrase": { + "message": "Generera lösenfras" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Värde måste vara mellan $MIN$ och $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Använd detta lösenord" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Använd detta användarnamn" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 27a160a9a3c..09873860bce 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Generate password" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Check if password has been exposed." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Generate password" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 7651ae5bc06..cf6aab45cec 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -547,12 +547,6 @@ "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "สร้างรหัสผ่าน" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "ตรวจสอบว่ารหัสผ่านถูกเปิดเผยหรือไม่" }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "สร้างรหัสผ่าน" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 7e6f3e6ab4e..4192861d6d1 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -547,12 +547,6 @@ "message": "Daraltmayı aç/kapat", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Parola oluştur" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Parolanız ele geçirilip geçirilmediğini kontrol edin." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Parola oluştur" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Parola üretildi" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Kullanıcı adı üretildi" + }, + "emailGenerated": { + "message": "E-posta üretildi" + }, "spinboxBoundariesHint": { "message": "Değer $MIN$ ile $MAX$ arasında olmalıdır.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Bu parolayı kullan" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Bu kullanıcı adını kullan" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Hassas bilgileri güvenle paylaşın", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Dosyaları ve verileri istediğiniz kişilerle, istediğiniz platformda paylaşın. Bilgileriniz başkalarının eline geçmemesi için uçtan şifrelenecektir.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Otomatik doldurmayla zaman kazanın" }, diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index a899bde3d04..00871794ac6 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -99,7 +99,7 @@ "message": "Позначити програму критичною" }, "applicationsMarkedAsCriticalSuccess": { - "message": "Applications marked as critical" + "message": "Позначені критичні програми" }, "application": { "message": "Програма" @@ -141,13 +141,13 @@ "message": "Ці учасники використовують у програмах слабкі, викриті, або повторювані паролі." }, "atRiskMembersDescriptionNone": { - "message": "These are no members logging into applications with weak, exposed, or reused passwords." + "message": "Немає учасників, які використовують у програмах слабкі, викриті, або повторювані паролі." }, "atRiskApplicationsDescription": { "message": "Ці програми мають слабкі, викриті, або повторювані паролі." }, "atRiskApplicationsDescriptionNone": { - "message": "These are no applications with weak, exposed, or reused passwords." + "message": "Немає програм, які мають слабкі, викриті, або повторювані паролі." }, "atRiskMembersDescriptionWithApp": { "message": "Ці учасники використовують у $APPNAME$ слабкі, викриті, або повторювані паролі.", @@ -159,7 +159,7 @@ } }, "atRiskMembersDescriptionWithAppNone": { - "message": "There are no at risk members for $APPNAME$.", + "message": "Немає учасників з ризиками для $APPNAME$.", "placeholders": { "appname": { "content": "$1", @@ -547,12 +547,6 @@ "message": "Згорнути/розгорнути", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Генерувати пароль" - }, - "generatePassphrase": { - "message": "Генерувати парольну фразу" - }, "checkPassword": { "message": "Перевірити чи пароль було викрито." }, @@ -4549,7 +4543,7 @@ "message": "Після оновлення вашого ключа шифрування вам необхідно вийти з системи і потім виконати повторний вхід у всіх програмах Bitwarden, які ви використовуєте. Збій при виході та повторному вході може призвести до пошкодження даних. Ми спробуємо завершити ваші сеанси автоматично, однак, цей процес може відбутися із затримкою." }, "updateEncryptionKeyAccountExportWarning": { - "message": "Any account restricted exports you have saved will become invalid." + "message": "Будь-які збережені експорти, обмежені обліковим записом, стануть недійсними." }, "subscription": { "message": "Передплата" @@ -6477,10 +6471,10 @@ "message": "Недійсний код підтвердження" }, "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + "message": "Головний пароль більше не є обов'язковим для учасників зазначеної організації. Підтвердьте вказаний нижче домен з адміністратором вашої організації." }, "keyConnectorDomain": { - "message": "Key Connector domain" + "message": "Домен Key Connector" }, "leaveOrganization": { "message": "Покинути організацію" @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Генерувати е-пошту" }, + "generatePassword": { + "message": "Генерувати пароль" + }, + "generatePassphrase": { + "message": "Генерувати парольну фразу" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Значення має бути між $MIN$ та $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Використати цей пароль" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Використати це ім'я користувача" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "Новий бізнес-підрозділ" }, + "sendsTitleNoItems": { + "message": "Безпечно надсилайте конфіденційну інформацію", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Безпечно діліться файлами й даними з ким завгодно, на будь-якій платформі. Ваша інформація наскрізно зашифрована та має обмежений доступ.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Швидко створюйте паролі" + }, + "generatorNudgeBodyOne": { + "message": "Легко створюйте надійні та унікальні паролі, натиснувши на", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "щоб зберегти свої записи в безпеці.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Легко створюйте надійні та унікальні паролі, натиснувши кнопку Генерувати пароль, щоб зберегти свої записи в безпеці.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Заощаджуйте час з автозаповненням" }, @@ -10609,6 +10649,6 @@ "message": "Оплата з банківським рахунком доступна тільки для клієнтів у США. Вам необхідно буде підтвердити свій банківський рахунок. Ми здійснимо мікродепозит протягом наступних 1–2 робочих днів. Введіть код дескриптора з цього депозиту на сторінці передплати провайдера, щоб підтвердити банківський рахунок. Неможливість засвідчення банківського рахунку призведе до втрати платежу та припинення вашої передплати." }, "clickPayWithPayPal": { - "message": "Please click the Pay with PayPal button to add your payment method." + "message": "Натисніть кнопку Сплатити з PayPal, щоб додати спосіб оплати." } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 78a536991a7..8218dc06ece 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -547,12 +547,6 @@ "message": "Ẩn bớt", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "Tạo mật khẩu" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "Kiểm tra xem mật khẩu có bị lộ không." }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "Tạo mật khẩu" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index eb186f11360..c126e0c533c 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -547,12 +547,6 @@ "message": "切换折叠", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "生成密码" - }, - "generatePassphrase": { - "message": "生成密码短语" - }, "checkPassword": { "message": "检查密码是否已暴露。" }, @@ -5884,7 +5878,7 @@ "message": "成功恢复组织的访问权限" }, "bulkFilteredMessage": { - "message": "已拒绝,不适用于此操作" + "message": "已排除,不适用于此操作" }, "nonCompliantMembersTitle": { "message": "不符合要求的成员" @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "生成电子邮箱" }, + "generatePassword": { + "message": "生成密码" + }, + "generatePassphrase": { + "message": "生成密码短语" + }, + "passwordGenerated": { + "message": "密码已生成" + }, + "passphraseGenerated": { + "message": "密码短语已生成" + }, + "usernameGenerated": { + "message": "用户名已生成" + }, + "emailGenerated": { + "message": "电子邮箱已生成" + }, "spinboxBoundariesHint": { "message": "值必须在 $MIN$ 和 $MAX$ 之间。", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "使用此密码" }, + "useThisPassphrase": { + "message": "使用此密码短语" + }, "useThisUsername": { "message": "使用此用户名" }, @@ -8446,7 +8461,7 @@ "message": "用户更新了通过账户恢复颁发的密码。" }, "activatedAccessToSecretsManager": { - "message": "已激活对机密管理器的访问权限", + "message": "激活了对机密管理器的访问权限", "description": "Confirmation message that one or more users gained access to Secrets Manager" }, "activateAccess": { @@ -10192,7 +10207,7 @@ "message": "描述符代码" }, "cannotRemoveViewOnlyCollections": { - "message": "您无法删除仅具有「查看」权限的集合:$COLLECTIONS$", + "message": "您无法移除仅具有「查看」权限的集合:$COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "新增业务单元" }, + "sendsTitleNoItems": { + "message": "安全地发送敏感信息", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "在任何平台上安全地与任何人共享文件和数据。您的信息将在限制曝光的同时保持端到端加密。", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "快速创建密码" + }, + "generatorNudgeBodyOne": { + "message": "一键创建强大且唯一的密码", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "帮助您保持登录安全。", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "点击「生成密码」按钮,轻松创建强大且唯一的密码,帮助您保持登录安全。", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "使用自动填充节省时间" }, diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 2f5567308d2..4be5bbcfbd3 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -547,12 +547,6 @@ "message": "切換折疊", "description": "Toggling an expand/collapse state." }, - "generatePassword": { - "message": "產生密碼" - }, - "generatePassphrase": { - "message": "Generate passphrase" - }, "checkPassword": { "message": "檢查密碼是否已暴露。" }, @@ -6824,6 +6818,24 @@ "generateEmail": { "message": "Generate email" }, + "generatePassword": { + "message": "產生密碼" + }, + "generatePassphrase": { + "message": "Generate passphrase" + }, + "passwordGenerated": { + "message": "Password generated" + }, + "passphraseGenerated": { + "message": "Passphrase generated" + }, + "usernameGenerated": { + "message": "Username generated" + }, + "emailGenerated": { + "message": "Email generated" + }, "spinboxBoundariesHint": { "message": "值必須介於 $MIN$ 及 $MAX$。", "description": "Explains spin box minimum and maximum values to the user", @@ -6887,6 +6899,9 @@ "useThisPassword": { "message": "使用此密碼" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "使用此使用者名稱" }, @@ -10553,6 +10568,31 @@ "newBusinessUnit": { "message": "New business unit" }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, "newLoginNudgeTitle": { "message": "Save time with autofill" }, From 85cef971c6944a6f9dc50a5f7226956b5037c128 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 30 May 2025 15:31:11 +0200 Subject: [PATCH 190/245] Add semver as dependency (#15005) --- .github/renovate.json5 | 1 + apps/cli/package.json | 1 + package-lock.json | 73 +++++++++++++++++++++++++++++++++--------- package.json | 1 + 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 453e5e29c44..8ad9ad1b360 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -204,6 +204,7 @@ "scopeguard", "security-framework", "security-framework-sys", + "semver", "serde", "serde_json", "simplelog", diff --git a/apps/cli/package.json b/apps/cli/package.json index eeebf4dad6f..4ac93d53c40 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -89,6 +89,7 @@ "papaparse": "5.5.3", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", + "semver": "7.7.2", "tldts": "7.0.1", "zxcvbn": "4.4.2" } diff --git a/package-lock.json b/package-lock.json index d4009fff06d..fbb4860eb64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,6 +65,7 @@ "qrcode-parser": "2.1.3", "qrious": "4.0.2", "rxjs": "7.8.1", + "semver": "7.7.2", "tabbable": "6.2.0", "tldts": "7.0.1", "utf-8-validate": "6.0.5", @@ -223,6 +224,7 @@ "papaparse": "5.5.3", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", + "semver": "7.7.2", "tldts": "7.0.1", "zxcvbn": "4.4.2" }, @@ -1588,6 +1590,19 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -2421,6 +2436,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@angular-eslint/schematics/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@angular-eslint/template-parser": { "version": "18.4.3", "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.4.3.tgz", @@ -2753,6 +2781,19 @@ "node": ">=14.0.0" } }, + "node_modules/@angular/build/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@angular/cdk": { "version": "18.2.14", "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.14.tgz", @@ -2920,6 +2961,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@angular/cli/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@angular/common": { "version": "18.2.13", "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.13.tgz", @@ -23198,19 +23252,6 @@ "semver": "^7.7.1" } }, - "node_modules/is-bun-module/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -34270,9 +34311,9 @@ } }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" diff --git a/package.json b/package.json index 84164696f46..0eaf07ee6d7 100644 --- a/package.json +++ b/package.json @@ -201,6 +201,7 @@ "qrcode-parser": "2.1.3", "qrious": "4.0.2", "rxjs": "7.8.1", + "semver": "7.7.2", "tabbable": "6.2.0", "tldts": "7.0.1", "utf-8-validate": "6.0.5", From 291341c987784a0ba3fcef1bc9d2211aa80e6ed7 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Fri, 30 May 2025 15:34:29 +0100 Subject: [PATCH 191/245] Changes the revoke message (#14963) --- .../members/free-bitwarden-families.component.ts | 2 +- apps/web/src/locales/en/messages.json | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts index 5b249683b57..0e3b682104e 100644 --- a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts +++ b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts @@ -160,7 +160,7 @@ export class FreeBitwardenFamiliesComponent implements OnInit { private async doRevokeSponsorship(sponsorship: OrganizationSponsorshipInvitesResponse) { const content = sponsorship.validUntil ? this.i18nService.t( - "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship", + "revokeActiveSponsorshipConfirmation", sponsorship.friendlyName, formatDate(sponsorship.validUntil, "MM/dd/yyyy", this.locale), ) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index a170612fab2..e1a2d3cbef2 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -10651,5 +10651,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } From 4290136a2a0cf42e0c854992fbcf24bf8e75a283 Mon Sep 17 00:00:00 2001 From: Jared McCannon Date: Fri, 30 May 2025 10:36:10 -0400 Subject: [PATCH 192/245] Fixed which collection observable was passed to the nested traverse. (#15008) --- .../admin-console/organizations/collections/vault.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts index 19373f193d9..45300b45fa5 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts @@ -425,7 +425,7 @@ export class VaultComponent implements OnInit, OnDestroy { ); const nestedCollections$ = combineLatest([ - this.allCollectionsWithoutUnassigned$, + allCollections$, this.configService.getFeatureFlag$(FeatureFlag.OptimizeNestedTraverseTypescript), ]).pipe( map( From d64ec01bd75c25fa4bbeb4666b002fae54930bd8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 08:48:07 -0700 Subject: [PATCH 193/245] [deps]: Update sonarsource/sonarqube-scan-action action to v5 (#14931) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index 585115ef6dd..59ef1e0734e 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -74,7 +74,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Scan with SonarCloud - uses: sonarsource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1 + uses: sonarsource/sonarqube-scan-action@2500896589ef8f7247069a56136f8dc177c27ccf # v5.2.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} with: From 06a480fc14ec1e5680eae32bde2c16b33f570916 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Fri, 30 May 2025 17:53:16 +0200 Subject: [PATCH 194/245] [PM-17501] Migrate send.component on web to use tailwind (#14940) * Replace usage of text-musted with tw-text-muted * Remove usage of class no-items --------- Co-authored-by: Daniel James Smith --- apps/web/src/app/tools/send/send.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/src/app/tools/send/send.component.html b/apps/web/src/app/tools/send/send.component.html index e55d5e56f78..1f220f4551e 100644 --- a/apps/web/src/app/tools/send/send.component.html +++ b/apps/web/src/app/tools/send/send.component.html @@ -3,7 +3,7 @@ @@ -183,10 +183,10 @@ -
+
From 4e07fd7666f2601812f81d6b691c312f7c4e6559 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 08:54:58 -0700 Subject: [PATCH 195/245] [deps]: Update anchore/scan-action action to v6 (#14928) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-web.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index 275b867390e..019647f594a 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -309,7 +309,7 @@ jobs: - name: Scan Docker image if: ${{ needs.setup.outputs.has_secrets == 'true' }} id: container-scan - uses: anchore/scan-action@869c549e657a088dc0441b08ce4fc0ecdac2bb65 # v5.3.0 + uses: anchore/scan-action@2c901ab7378897c01b8efaa2d0c9bf519cc64b9e # v6.2.0 with: image: ${{ steps.image-name.outputs.name }} fail-build: false From 5eb8d7b181936220e79cad1d1630701eba813089 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham Date: Fri, 30 May 2025 12:38:40 -0400 Subject: [PATCH 196/245] [CL-208][CL-339] Enhance Storybook docs pages (#14838) * rearrange button docs * Enhance avatar docs * Enhance badge docs * Enhance banner docs * add util to format args for snippets * update banner snippets * WIP * bind boolean args so they work correctly in Storybook * simplify button stories * Update callout docs * use title component for checkbox docs * use title and description component for chip select docs * update color password story docs * update disclosure docs * add import to icon docs * updated icon-button docs * update link docs * Update prgress docs * updated search field docs * remove html type definitions * add import for progress * updated toast docs * remove example from docs. format args for snippet * Update badges docs * handle array arg values correctly * Update badges list docs * fix dupe key error from taost story * remove unnecessary typeof check * remove banner usage example * add breadcrumbs import statement and jsdoc * add color password import statement * fixing type mismaches * fix typos * Add missing generics to format function * fix typo * update callout icon spacing to match Figma * add back max width container --- .storybook/format-args-for-code-snippet.ts | 33 ++++++ .storybook/preview.tsx | 7 +- .../components/src/avatar/avatar.component.ts | 6 ++ libs/components/src/avatar/avatar.mdx | 12 +-- libs/components/src/avatar/avatar.stories.ts | 16 +++ .../src/badge-list/badge-list.stories.ts | 3 +- libs/components/src/badge/badge.component.ts | 15 ++- libs/components/src/badge/badge.mdx | 18 +--- libs/components/src/badge/badge.stories.ts | 100 ++++++++++-------- .../components/src/banner/banner.component.ts | 15 ++- libs/components/src/banner/banner.mdx | 23 ++-- libs/components/src/banner/banner.stories.ts | 37 ++++--- .../src/breadcrumbs/breadcrumbs.component.ts | 5 + .../src/breadcrumbs/breadcrumbs.mdx | 11 +- libs/components/src/button/button.mdx | 39 ++++--- libs/components/src/button/button.stories.ts | 73 +++++++------ .../src/callout/callout.component.html | 5 +- .../src/callout/callout.component.ts | 5 + libs/components/src/callout/callout.mdx | 10 +- .../components/src/callout/callout.stories.ts | 23 ++-- libs/components/src/checkbox/checkbox.mdx | 5 +- .../src/chip-select/chip-select.component.ts | 3 + .../src/chip-select/chip-select.mdx | 7 +- .../color-password.component.ts | 6 +- .../src/color-password/color-password.mdx | 11 +- .../color-password/color-password.stories.ts | 6 +- .../src/disclosure/disclosure.component.ts | 24 +++++ libs/components/src/disclosure/disclosure.mdx | 32 +----- .../src/icon-button/icon-button.component.ts | 6 ++ .../src/icon-button/icon-button.mdx | 14 +-- .../src/icon-button/icon-button.stories.ts | 48 +++------ libs/components/src/icon/icon.mdx | 4 + libs/components/src/link/link.directive.ts | 8 ++ libs/components/src/link/link.mdx | 15 +-- libs/components/src/link/link.stories.ts | 10 ++ .../src/progress/progress.component.ts | 15 +-- libs/components/src/progress/progress.mdx | 10 +- .../src/progress/progress.stories.ts | 17 +++ libs/components/src/search/search.mdx | 4 +- libs/components/src/search/search.stories.ts | 7 +- libs/components/src/toast/toast.mdx | 18 ++-- libs/components/src/toast/toast.stories.ts | 26 +++-- libs/components/src/toast/toastr.component.ts | 3 + tsconfig.eslint.json | 1 + 44 files changed, 454 insertions(+), 302 deletions(-) create mode 100644 .storybook/format-args-for-code-snippet.ts diff --git a/.storybook/format-args-for-code-snippet.ts b/.storybook/format-args-for-code-snippet.ts new file mode 100644 index 00000000000..bf36c153c0a --- /dev/null +++ b/.storybook/format-args-for-code-snippet.ts @@ -0,0 +1,33 @@ +import { argsToTemplate, StoryObj } from "@storybook/angular"; + +type RenderArgType = StoryObj["args"]; + +export const formatArgsForCodeSnippet = >( + args: RenderArgType, +) => { + const nonNullArgs = Object.entries(args as ComponentType).filter( + ([_, value]) => value !== null && value !== undefined, + ); + const functionArgs = nonNullArgs.filter(([_, value]) => typeof value === "function"); + const argsToFormat = nonNullArgs.filter(([_, value]) => typeof value !== "function"); + + const argsToTemplateIncludeKeys = [...functionArgs].map( + ([key, _]) => key as keyof RenderArgType, + ); + + const formattedNonFunctionArgs = argsToFormat + .map(([key, value]) => { + if (typeof value === "boolean") { + return `[${key}]="${value}"`; + } + + if (Array.isArray(value)) { + const formattedArray = value.map((v) => `'${v}'`).join(", "); + return `[${key}]="[${formattedArray}]"`; + } + return `${key}="${value}"`; + }) + .join(" "); + + return `${formattedNonFunctionArgs} ${argsToTemplate(args as ComponentType, { include: argsToTemplateIncludeKeys })}`; +}; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index a948fce0428..59b5287f3a3 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -41,7 +41,12 @@ const preview: Preview = { order: ["Documentation", ["Introduction", "Colors", "Icons"], "Component Library"], }, }, - docs: { source: { type: "dynamic", excludeDecorators: true } }, + docs: { + source: { + type: "dynamic", + excludeDecorators: true, + }, + }, backgrounds: { disable: true, }, diff --git a/libs/components/src/avatar/avatar.component.ts b/libs/components/src/avatar/avatar.component.ts index 554f55636fc..c66bba1c462 100644 --- a/libs/components/src/avatar/avatar.component.ts +++ b/libs/components/src/avatar/avatar.component.ts @@ -16,6 +16,12 @@ const SizeClasses: Record = { xsmall: ["tw-h-6", "tw-w-6"], }; +/** + * Avatars display a unique color that helps a user visually recognize their logged in account. + + * A variance in color across the avatar component is important as it is used in Account Switching as a + * visual indicator to recognize which of a personal or work account a user is logged into. +*/ @Component({ selector: "bit-avatar", template: `@if (src) { diff --git a/libs/components/src/avatar/avatar.mdx b/libs/components/src/avatar/avatar.mdx index 627ba526ed9..bbf356f96fa 100644 --- a/libs/components/src/avatar/avatar.mdx +++ b/libs/components/src/avatar/avatar.mdx @@ -1,15 +1,15 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Description, Meta, Canvas, Primary, Controls, Title } from "@storybook/addon-docs"; import * as stories from "./avatar.stories"; -# Avatar +```ts +import { AvatarModule } from "@bitwarden/components"; +``` -Avatars display a unique color that helps a user visually recognize their logged in account. - -A variance in color across the avatar component is important as it is used in Account Switching as a -visual indicator to recognize which of a personal or work account a user is logged into. + +<Description /> <Primary /> <Controls /> diff --git a/libs/components/src/avatar/avatar.stories.ts b/libs/components/src/avatar/avatar.stories.ts index 19a6f86d89c..9b0d4e4aa8c 100644 --- a/libs/components/src/avatar/avatar.stories.ts +++ b/libs/components/src/avatar/avatar.stories.ts @@ -1,5 +1,7 @@ import { Meta, StoryObj } from "@storybook/angular"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; + import { AvatarComponent } from "./avatar.component"; export default { @@ -21,42 +23,56 @@ export default { type Story = StoryObj<AvatarComponent>; export const Default: Story = { + render: (args) => { + return { + props: args, + template: ` + <bit-avatar ${formatArgsForCodeSnippet<AvatarComponent>(args)}></bit-avatar> + `, + }; + }, args: { color: "#175ddc", }, }; export const Large: Story = { + ...Default, args: { size: "large", }, }; export const Small: Story = { + ...Default, args: { size: "small", }, }; export const LightBackground: Story = { + ...Default, args: { color: "#d2ffcf", }, }; export const Border: Story = { + ...Default, args: { border: true, }, }; export const ColorByID: Story = { + ...Default, args: { id: "236478", }, }; export const ColorByText: Story = { + ...Default, args: { text: "Jason Doe", }, diff --git a/libs/components/src/badge-list/badge-list.stories.ts b/libs/components/src/badge-list/badge-list.stories.ts index f69ecde8377..504871f9509 100644 --- a/libs/components/src/badge-list/badge-list.stories.ts +++ b/libs/components/src/badge-list/badge-list.stories.ts @@ -2,6 +2,7 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; import { BadgeModule } from "../badge"; import { SharedModule } from "../shared"; import { I18nMockService } from "../utils/i18n-mock.service"; @@ -44,7 +45,7 @@ export const Default: Story = { render: (args) => ({ props: args, template: ` - <bit-badge-list [variant]="variant" [maxItems]="maxItems" [items]="items" [truncate]="truncate"></bit-badge-list> + <bit-badge-list ${formatArgsForCodeSnippet<BadgeListComponent>(args)}></bit-badge-list> `, }), diff --git a/libs/components/src/badge/badge.component.ts b/libs/components/src/badge/badge.component.ts index 893257ff225..3612827eff2 100644 --- a/libs/components/src/badge/badge.component.ts +++ b/libs/components/src/badge/badge.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, ElementRef, HostBinding, Input } from "@angular/core"; @@ -45,7 +43,18 @@ const hoverStyles: Record<BadgeVariant, string[]> = { "hover:!tw-text-contrast", ], }; +/** + * Badges are primarily used as labels, counters, and small buttons. + * Typically Badges are only used with text set to `text-xs`. If additional sizes are needed, the component configurations may be reviewed and adjusted. + + * The Badge directive can be used on a `<span>` (non clickable events), or an `<a>` or `<button>` tag + + * > `NOTE:` The Focus and Hover states only apply to badges used for interactive events. + * + * > `NOTE:` The `disabled` state only applies to buttons. + * +*/ @Component({ selector: "span[bitBadge], a[bitBadge], button[bitBadge]", providers: [{ provide: FocusableElement, useExisting: BadgeComponent }], @@ -89,7 +98,7 @@ export class BadgeComponent implements FocusableElement { if (this.title !== undefined) { return this.title; } - return this.truncate ? this.el.nativeElement.textContent.trim() : null; + return this.truncate ? this?.el?.nativeElement?.textContent?.trim() : null; } /** diff --git a/libs/components/src/badge/badge.mdx b/libs/components/src/badge/badge.mdx index 55f32183899..957a3256cbb 100644 --- a/libs/components/src/badge/badge.mdx +++ b/libs/components/src/badge/badge.mdx @@ -1,4 +1,4 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./badge.stories"; @@ -8,25 +8,15 @@ import * as stories from "./badge.stories"; import { BadgeModule } from "@bitwarden/components"; ``` -# Badge - -Badges are primarily used as labels, counters, and small buttons. - -Typically Badges are only used with text set to `text-xs`. If additional sizes are needed, the -component configurations may be reviewed and adjusted. - -The Badge directive can be used on a `<span>` (non clickable events), or an `<a>` or `<button>` tag -for interactive events. The Focus and Hover states only apply to badges used for interactive events. -The `disabled` state only applies to buttons. - -The story below uses the `<button>` element to demonstrate all the possible states. +<Title /> +<Description /> <Primary /> <Controls /> ## Styles -### Primary +### Default / Primary The primary badge is used to indicate an active status (example: device management page) or provide additional information (example: type of emergency access granted). diff --git a/libs/components/src/badge/badge.stories.ts b/libs/components/src/badge/badge.stories.ts index 6473ba8c867..a151547ef6a 100644 --- a/libs/components/src/badge/badge.stories.ts +++ b/libs/components/src/badge/badge.stories.ts @@ -1,6 +1,8 @@ import { CommonModule } from "@angular/common"; import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; + import { BadgeComponent } from "./badge.component"; export default { @@ -12,7 +14,6 @@ export default { }), ], args: { - variant: "primary", truncate: false, }, parameters: { @@ -25,45 +26,11 @@ export default { type Story = StoryObj<BadgeComponent>; -export const Variants: Story = { +export const Default: Story = { render: (args) => ({ props: args, template: /*html*/ ` - <span class="tw-text-main tw-mx-1">Default</span> - <button class="tw-mx-1" bitBadge variant="primary" [truncate]="truncate">Primary</button> - <button class="tw-mx-1" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> - <button class="tw-mx-1" bitBadge variant="success" [truncate]="truncate">Success</button> - <button class="tw-mx-1" bitBadge variant="danger" [truncate]="truncate">Danger</button> - <button class="tw-mx-1" bitBadge variant="warning" [truncate]="truncate">Warning</button> - <button class="tw-mx-1" bitBadge variant="info" [truncate]="truncate">Info</button> - <button class="tw-mx-1" bitBadge variant="notification" [truncate]="truncate">Notification</button> - <br/><br/> - <span class="tw-text-main tw-mx-1">Hover</span> - <button class="tw-mx-1 tw-test-hover" bitBadge variant="primary" [truncate]="truncate">Primary</button> - <button class="tw-mx-1 tw-test-hover" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> - <button class="tw-mx-1 tw-test-hover" bitBadge variant="success" [truncate]="truncate">Success</button> - <button class="tw-mx-1 tw-test-hover" bitBadge variant="danger" [truncate]="truncate">Danger</button> - <button class="tw-mx-1 tw-test-hover" bitBadge variant="warning" [truncate]="truncate">Warning</button> - <button class="tw-mx-1 tw-test-hover" bitBadge variant="info" [truncate]="truncate">Info</button> - <button class="tw-mx-1 tw-test-hover" bitBadge variant="notification" [truncate]="truncate">Notification</button> - <br/><br/> - <span class="tw-text-main tw-mx-1">Focus Visible</span> - <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="primary" [truncate]="truncate">Primary</button> - <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> - <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="success" [truncate]="truncate">Success</button> - <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="danger" [truncate]="truncate">Danger</button> - <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="warning" [truncate]="truncate">Warning</button> - <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="info" [truncate]="truncate">Info</button> - <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="notification" [truncate]="truncate">Notification</button> - <br/><br/> - <span class="tw-text-main tw-mx-1">Disabled</span> - <button disabled class="tw-mx-1" bitBadge variant="primary" [truncate]="truncate">Primary</button> - <button disabled class="tw-mx-1" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> - <button disabled class="tw-mx-1" bitBadge variant="success" [truncate]="truncate">Success</button> - <button disabled class="tw-mx-1" bitBadge variant="danger" [truncate]="truncate">Danger</button> - <button disabled class="tw-mx-1" bitBadge variant="warning" [truncate]="truncate">Warning</button> - <button disabled class="tw-mx-1" bitBadge variant="info" [truncate]="truncate">Info</button> - <button disabled class="tw-mx-1" bitBadge variant="notification" [truncate]="truncate">Notification</button> + <span bitBadge ${formatArgsForCodeSnippet<BadgeComponent>(args)}>Badge containing lengthy text</span> `, }), }; @@ -72,11 +39,17 @@ export const Primary: Story = { render: (args) => ({ props: args, template: /*html*/ ` - <span class="tw-text-main">Span </span><span bitBadge [variant]="variant" [truncate]="truncate">Badge containing lengthy text</span> - <br /><br /> - <span class="tw-text-main">Link </span><a href="#" bitBadge [variant]="variant" [truncate]="truncate">Badge</a> - <br /><br /> - <span class="tw-text-main">Button </span><button bitBadge [variant]="variant" [truncate]="truncate">Badge</button> + <div class="tw-flex tw-flex-col tw-gap-4"> + <div class="tw-flex tw-items-center tw-gap-2"> + <span class="tw-text-main">span</span><span bitBadge ${formatArgsForCodeSnippet<BadgeComponent>(args)}>Badge containing lengthy text</span> + </div> + <div class="tw-flex tw-items-center tw-gap-2"> + <span class="tw-text-main">link </span><a href="#" bitBadge ${formatArgsForCodeSnippet<BadgeComponent>(args)}>Badge</a> + </div> + <div class="tw-flex tw-items-center tw-gap-2"> + <span class="tw-text-main">button </span><button bitBadge ${formatArgsForCodeSnippet<BadgeComponent>(args)}>Badge</button> + </div> + </div> `, }), }; @@ -129,3 +102,46 @@ export const Truncated: Story = { truncate: true, }, }; + +export const VariantsAndInteractionStates: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + <span class="tw-text-main tw-mx-1">Default</span> + <button class="tw-mx-1" bitBadge variant="primary" [truncate]="truncate">Primary</button> + <button class="tw-mx-1" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> + <button class="tw-mx-1" bitBadge variant="success" [truncate]="truncate">Success</button> + <button class="tw-mx-1" bitBadge variant="danger" [truncate]="truncate">Danger</button> + <button class="tw-mx-1" bitBadge variant="warning" [truncate]="truncate">Warning</button> + <button class="tw-mx-1" bitBadge variant="info" [truncate]="truncate">Info</button> + <button class="tw-mx-1" bitBadge variant="notification" [truncate]="truncate">Notification</button> + <br/><br/> + <span class="tw-text-main tw-mx-1">Hover</span> + <button class="tw-mx-1 tw-test-hover" bitBadge variant="primary" [truncate]="truncate">Primary</button> + <button class="tw-mx-1 tw-test-hover" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> + <button class="tw-mx-1 tw-test-hover" bitBadge variant="success" [truncate]="truncate">Success</button> + <button class="tw-mx-1 tw-test-hover" bitBadge variant="danger" [truncate]="truncate">Danger</button> + <button class="tw-mx-1 tw-test-hover" bitBadge variant="warning" [truncate]="truncate">Warning</button> + <button class="tw-mx-1 tw-test-hover" bitBadge variant="info" [truncate]="truncate">Info</button> + <button class="tw-mx-1 tw-test-hover" bitBadge variant="notification" [truncate]="truncate">Notification</button> + <br/><br/> + <span class="tw-text-main tw-mx-1">Focus Visible</span> + <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="primary" [truncate]="truncate">Primary</button> + <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> + <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="success" [truncate]="truncate">Success</button> + <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="danger" [truncate]="truncate">Danger</button> + <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="warning" [truncate]="truncate">Warning</button> + <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="info" [truncate]="truncate">Info</button> + <button class="tw-mx-1 tw-test-focus-visible" bitBadge variant="notification" [truncate]="truncate">Notification</button> + <br/><br/> + <span class="tw-text-main tw-mx-1">Disabled</span> + <button disabled class="tw-mx-1" bitBadge variant="primary" [truncate]="truncate">Primary</button> + <button disabled class="tw-mx-1" bitBadge variant="secondary" [truncate]="truncate">Secondary</button> + <button disabled class="tw-mx-1" bitBadge variant="success" [truncate]="truncate">Success</button> + <button disabled class="tw-mx-1" bitBadge variant="danger" [truncate]="truncate">Danger</button> + <button disabled class="tw-mx-1" bitBadge variant="warning" [truncate]="truncate">Warning</button> + <button disabled class="tw-mx-1" bitBadge variant="info" [truncate]="truncate">Info</button> + <button disabled class="tw-mx-1" bitBadge variant="notification" [truncate]="truncate">Notification</button> + `, + }), +}; diff --git a/libs/components/src/banner/banner.component.ts b/libs/components/src/banner/banner.component.ts index a7b710d6a74..a6719f25989 100644 --- a/libs/components/src/banner/banner.component.ts +++ b/libs/components/src/banner/banner.component.ts @@ -7,15 +7,24 @@ import { I18nPipe } from "@bitwarden/ui-common"; import { IconButtonModule } from "../icon-button"; -type BannerTypes = "premium" | "info" | "warning" | "danger"; +type BannerType = "premium" | "info" | "warning" | "danger"; -const defaultIcon: Record<BannerTypes, string> = { +const defaultIcon: Record<BannerType, string> = { premium: "bwi-star", info: "bwi-info-circle", warning: "bwi-exclamation-triangle", danger: "bwi-error", }; +/** + * Banners are used for important communication with the user that needs to be seen right away, but has + * little effect on the experience. Banners appear at the top of the user's screen on page load and + * persist across all pages a user navigates to. + * - They should always be dismissible and never use a timeout. If a user dismisses a banner, it should not reappear during that same active session. + * - Use banners sparingly, as they can feel intrusive to the user if they appear unexpectedly. Their effectiveness may decrease if too many are used. + * - Avoid stacking multiple banners. + * - Banners can contain a button or anchor that uses the `bitLink` directive with `linkType="secondary"`. + */ @Component({ selector: "bit-banner", templateUrl: "./banner.component.html", @@ -23,7 +32,7 @@ const defaultIcon: Record<BannerTypes, string> = { imports: [CommonModule, IconButtonModule, I18nPipe], }) export class BannerComponent implements OnInit { - @Input("bannerType") bannerType: BannerTypes = "info"; + @Input("bannerType") bannerType: BannerType = "info"; @Input() icon: string; @Input() useAlertRole = true; @Input() showClose = true; diff --git a/libs/components/src/banner/banner.mdx b/libs/components/src/banner/banner.mdx index 67fb796a548..f37fe90e117 100644 --- a/libs/components/src/banner/banner.mdx +++ b/libs/components/src/banner/banner.mdx @@ -1,25 +1,16 @@ -import { Meta, Controls, Canvas, Primary } from "@storybook/addon-docs"; +import { Canvas, Controls, Description, Meta, Primary, Title } from "@storybook/addon-docs"; import * as stories from "./banner.stories"; <Meta of={stories} /> -# Banner - -Banners are used for important communication with the user that needs to be seen right away, but has -little effect on the experience. Banners appear at the top of the user's screen on page load and -persist across all pages a user navigates to. - -- They should always be dismissible and never use a timeout. If a user dismisses a banner, it should - not reappear during that same active session. -- Use banners sparingly, as they can feel intrusive to the user if they appear unexpectedly. Their - effectiveness may decrease if too many are used. -- Avoid stacking multiple banners. -- Banners can contain a button or anchor that uses the `bitLink` directive with - `linkType="secondary"`. +```ts +import { BannerModule } from "@bitwarden/components"; +``` +<Title /> +<Description /> <Primary /> - <Controls /> ## Types @@ -56,5 +47,5 @@ Rarely used, but may be used to alert users over critical messages or very outda ## Accessibility Banners sets the `role="status"` and `aria-live="polite"` attributes to ensure screen readers -announce the content prior to the test of the page. This behaviour can be disabled by setting +announce the content prior to the test of the page. This behavior can be disabled by setting `[useAlertRole]="false"`. diff --git a/libs/components/src/banner/banner.stories.ts b/libs/components/src/banner/banner.stories.ts index 105d30bc04a..8338c9240b9 100644 --- a/libs/components/src/banner/banner.stories.ts +++ b/libs/components/src/banner/banner.stories.ts @@ -2,6 +2,7 @@ import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; import { IconButtonModule } from "../icon-button"; import { LinkModule } from "../link"; import { SharedModule } from "../shared/shared.module"; @@ -44,48 +45,50 @@ export default { type Story = StoryObj<BannerComponent>; +export const Base: Story = { + render: (args) => { + return { + props: args, + template: ` + <bit-banner ${formatArgsForCodeSnippet<BannerComponent>(args)}> + Content Really Long Text Lorem Ipsum Ipsum Ipsum + <button bitLink linkType="secondary">Button</button> + </bit-banner> + `, + }; + }, +}; + export const Premium: Story = { + ...Base, args: { bannerType: "premium", }, - render: (args) => ({ - props: args, - template: ` - <bit-banner [bannerType]="bannerType" (onClose)="onClose($event)" [showClose]=showClose> - Content Really Long Text Lorem Ipsum Ipsum Ipsum - <button bitLink linkType="secondary">Button</button> - </bit-banner> - `, - }), -}; - -Premium.args = { - bannerType: "premium", }; export const Info: Story = { - ...Premium, + ...Base, args: { bannerType: "info", }, }; export const Warning: Story = { - ...Premium, + ...Base, args: { bannerType: "warning", }, }; export const Danger: Story = { - ...Premium, + ...Base, args: { bannerType: "danger", }, }; export const HideClose: Story = { - ...Premium, + ...Base, args: { showClose: false, }, diff --git a/libs/components/src/breadcrumbs/breadcrumbs.component.ts b/libs/components/src/breadcrumbs/breadcrumbs.component.ts index 6e8fbf5c25a..24265212969 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.component.ts +++ b/libs/components/src/breadcrumbs/breadcrumbs.component.ts @@ -8,6 +8,11 @@ import { MenuModule } from "../menu"; import { BreadcrumbComponent } from "./breadcrumb.component"; +/** + * Breadcrumbs are used to help users understand where they are in a products navigation. Typically + * Bitwarden uses this component to indicate the user's current location in a set of data organized in + * containers (Collections, Folders, or Projects). + */ @Component({ selector: "bit-breadcrumbs", templateUrl: "./breadcrumbs.component.html", diff --git a/libs/components/src/breadcrumbs/breadcrumbs.mdx b/libs/components/src/breadcrumbs/breadcrumbs.mdx index 1ea0aff8c36..cd1d0226387 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.mdx +++ b/libs/components/src/breadcrumbs/breadcrumbs.mdx @@ -1,14 +1,15 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./breadcrumbs.stories"; <Meta of={stories} /> -# Breadcrumbs +```ts +import { BreadcrumbsModule } from "@bitwarden/components"; +``` -Breadcrumbs are used to help users understand where they are in a products navigation. Typically -Bitwarden uses this component to indicate the user's current location in a set of data organized in -containers (Collections, Folders, or Projects). +<Title /> +<Description /> <Primary /> <Controls /> diff --git a/libs/components/src/button/button.mdx b/libs/components/src/button/button.mdx index 61874922fc7..b0f347ba337 100644 --- a/libs/components/src/button/button.mdx +++ b/libs/components/src/button/button.mdx @@ -1,4 +1,12 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { + Markdown, + Meta, + Canvas, + Primary, + Controls, + Title, + Description, +} from "@storybook/addon-docs"; import * as stories from "./button.stories"; @@ -8,10 +16,9 @@ import * as stories from "./button.stories"; import { ButtonModule } from "@bitwarden/components"; ``` -# Button +<Title /> -Buttons are interactive elements that can be triggered using a mouse, keyboard, or touch. They are -used to indicate actions that can be performed by a user such as submitting a form. +### Default / Secondary <Primary /> @@ -30,7 +37,7 @@ takes: ### Groups -Groups of buttons should be seperated by a `0.5` rem gap. Usually acomplished by using the +Groups of buttons should be separated by a `0.5` rem gap. Usually accomplished by using the `tw-gap-2` class in the button group container. Groups within page content, dialog footers or forms should have the `primary` call to action placed @@ -41,26 +48,24 @@ right. There are 3 main styles for the button: Primary, Secondary, and Danger. -### Primary +### Default / Secondary -<Canvas of={stories.Primary} /> +The secondary styling(shown above) should be used for secondary calls to action. An action is +"secondary" if it relates indirectly to the purpose of a page. There may be multiple secondary +buttons next to each other; however, generally there should only be 1 or 2 calls to action per page. + +### Primary Use the primary button styling for all Primary call to actions. An action is "primary" if it relates to the main purpose of a page. There should never be 2 primary styled buttons next to each other. -### Secondary - -<Canvas of={stories.Secondary} /> - -The secondary styling should be used for secondary calls to action. An action is "secondary" if it -relates indirectly to the purpose of a page. There may be multiple secondary buttons next to each -other; however, generally there should only be 1 or 2 calls to action per page. +<Canvas of={stories.Primary} /> ### Danger -<Canvas of={stories.Danger} /> +Use the danger styling only in settings when the user may perform a permanent destructive action. -Use the danger styling only in settings when the user may preform a permanent action. +<Canvas of={stories.Danger} /> ## Disabled UI @@ -114,7 +119,7 @@ success toast). ### Submit and async actions Both submit and async action buttons use a loading button state while an action is taken. If your -button is preforming a long running task in the background like a server API call, be sure to review +button is performing a long running task in the background like a server API call, be sure to review the [Async Actions Directive](?path=/story/component-library-async-actions-overview--page). <Canvas of={stories.Loading} /> diff --git a/libs/components/src/button/button.stories.ts b/libs/components/src/button/button.stories.ts index 759bd1a352c..d0a4354f374 100644 --- a/libs/components/src/button/button.stories.ts +++ b/libs/components/src/button/button.stories.ts @@ -1,15 +1,15 @@ import { Meta, StoryObj } from "@storybook/angular"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; + import { ButtonComponent } from "./button.component"; export default { title: "Component Library/Button", component: ButtonComponent, args: { - buttonType: "primary", disabled: false, loading: false, - size: "default", }, argTypes: { size: { @@ -27,40 +27,27 @@ export default { type Story = StoryObj<ButtonComponent>; -export const Primary: Story = { +export const Default: Story = { render: (args) => ({ props: args, template: /*html*/ ` - <div class="tw-flex tw-gap-4 tw-mb-6 tw-items-center"> - <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block">Button</button> - <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover">Button:hover</button> - <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-focus-visible">Button:focus-visible</button> - <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover tw-test-focus-visible">Button:hover:focus-visible</button> - <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-active">Button:active</button> - </div> - <div class="tw-flex tw-gap-4 tw-items-center"> - <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block">Anchor</a> - <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover">Anchor:hover</a> - <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-focus-visible">Anchor:focus-visible</a> - <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover tw-test-focus-visible">Anchor:hover:focus-visible</a> - <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-active">Anchor:active</a> - </div> + <button bitButton ${formatArgsForCodeSnippet<ButtonComponent>(args)}>Button</button> `, }), - args: { - buttonType: "primary", - }, -}; - -export const Secondary: Story = { - ...Primary, args: { buttonType: "secondary", }, }; +export const Primary: Story = { + ...Default, + args: { + buttonType: "primary", + }, +}; + export const Danger: Story = { - ...Primary, + ...Default, args: { buttonType: "danger", }, @@ -83,16 +70,8 @@ export const Small: Story = { }; export const Loading: Story = { - render: (args) => ({ - props: args, - template: ` - <button bitButton [disabled]="disabled" [loading]="loading" [block]="block" buttonType="primary" class="tw-mr-2">Primary</button> - <button bitButton [disabled]="disabled" [loading]="loading" [block]="block" buttonType="secondary" class="tw-mr-2">Secondary</button> - <button bitButton [disabled]="disabled" [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button> - `, - }), + ...Default, args: { - disabled: false, loading: true, }, }; @@ -101,7 +80,6 @@ export const Disabled: Story = { ...Loading, args: { disabled: true, - loading: false, }, }; @@ -165,3 +143,28 @@ export const WithIcon: Story = { `, }), }; + +export const InteractionStates: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + <div class="tw-flex tw-gap-4 tw-mb-6 tw-items-center"> + <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block">Button</button> + <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover">Button:hover</button> + <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-focus-visible">Button:focus-visible</button> + <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover tw-test-focus-visible">Button:hover:focus-visible</button> + <button bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-active">Button:active</button> + </div> + <div class="tw-flex tw-gap-4 tw-items-center"> + <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block">Anchor</a> + <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover">Anchor:hover</a> + <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-focus-visible">Anchor:focus-visible</a> + <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-hover tw-test-focus-visible">Anchor:hover:focus-visible</a> + <a href="#" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size" [block]="block" class="tw-test-active">Anchor:active</a> + </div> + `, + }), + args: { + buttonType: "primary", + }, +}; diff --git a/libs/components/src/callout/callout.component.html b/libs/components/src/callout/callout.component.html index bb7f918df32..4e7b5f2a0cc 100644 --- a/libs/components/src/callout/callout.component.html +++ b/libs/components/src/callout/callout.component.html @@ -4,7 +4,10 @@ [attr.aria-labelledby]="titleId" > @if (title) { - <header id="{{ titleId }}" class="tw-mb-1 tw-mt-0 tw-text-base tw-font-semibold"> + <header + id="{{ titleId }}" + class="tw-mb-1 tw-mt-0 tw-text-base tw-font-semibold tw-flex tw-gap-2 tw-items-center" + > @if (icon) { <i class="bwi" [ngClass]="[icon, headerClass]" aria-hidden="true"></i> } diff --git a/libs/components/src/callout/callout.component.ts b/libs/components/src/callout/callout.component.ts index 6ffd8d2d0ec..e1bd7f1a596 100644 --- a/libs/components/src/callout/callout.component.ts +++ b/libs/components/src/callout/callout.component.ts @@ -24,6 +24,11 @@ const defaultI18n: Partial<Record<CalloutTypes, string>> = { // Increments for each instance of this component let nextId = 0; +/** + * Callouts are used to communicate important information to the user. Callouts should be used + * sparingly, as they command a large amount of visual attention. Avoid using more than 1 callout in + * the same location. + */ @Component({ selector: "bit-callout", templateUrl: "callout.component.html", diff --git a/libs/components/src/callout/callout.mdx b/libs/components/src/callout/callout.mdx index 160b1e1cc33..a1254b3f691 100644 --- a/libs/components/src/callout/callout.mdx +++ b/libs/components/src/callout/callout.mdx @@ -1,4 +1,4 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./callout.stories"; @@ -8,11 +8,11 @@ import { CalloutModule } from "@bitwarden/components"; <Meta of={stories} /> -# Callouts +<Title /> +<Description /> -Callouts are used to communicate important information to the user. Callouts should be used -sparingly, as they command a large amount of visual attention. Avoid using more than 1 callout in -the same location. +<Primary /> +<Controls /> ## Styles diff --git a/libs/components/src/callout/callout.stories.ts b/libs/components/src/callout/callout.stories.ts index 3101d4316f1..5f22bf9570a 100644 --- a/libs/components/src/callout/callout.stories.ts +++ b/libs/components/src/callout/callout.stories.ts @@ -2,6 +2,7 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; import { I18nMockService } from "../utils/i18n-mock.service"; import { CalloutComponent } from "./callout.component"; @@ -24,9 +25,6 @@ export default { ], }), ], - args: { - type: "warning", - }, parameters: { design: { type: "figma", @@ -37,36 +35,35 @@ export default { type Story = StoryObj<CalloutComponent>; -export const Success: Story = { +export const Info: Story = { render: (args) => ({ props: args, template: ` - <bit-callout [type]="type" [title]="title">Content</bit-callout> + <bit-callout ${formatArgsForCodeSnippet<CalloutComponent>(args)}>Content</bit-callout> `, }), args: { - type: "success", - title: "Success", + title: "Title", }, }; -export const Info: Story = { - ...Success, +export const Success: Story = { + ...Info, args: { - type: "info", - title: "Info", + ...Info.args, + type: "success", }, }; export const Warning: Story = { - ...Success, + ...Info, args: { type: "warning", }, }; export const Danger: Story = { - ...Success, + ...Info, args: { type: "danger", }, diff --git a/libs/components/src/checkbox/checkbox.mdx b/libs/components/src/checkbox/checkbox.mdx index f3ce0d8fd07..ba5de4d234a 100644 --- a/libs/components/src/checkbox/checkbox.mdx +++ b/libs/components/src/checkbox/checkbox.mdx @@ -1,4 +1,4 @@ -import { Meta, Canvas, Source, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Source, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./checkbox.stories"; @@ -8,7 +8,8 @@ import * as stories from "./checkbox.stories"; import { CheckboxModule } from "@bitwarden/components"; ``` -# Checkbox +<Title /> +<Description /> <Primary /> <Controls /> diff --git a/libs/components/src/chip-select/chip-select.component.ts b/libs/components/src/chip-select/chip-select.component.ts index d1f3bba2624..270249ade0c 100644 --- a/libs/components/src/chip-select/chip-select.component.ts +++ b/libs/components/src/chip-select/chip-select.component.ts @@ -33,6 +33,9 @@ export type ChipSelectOption<T> = Option<T> & { children?: ChipSelectOption<T>[]; }; +/** + * `<bit-chip-select>` is a select element that is commonly used to filter items in lists or tables. + */ @Component({ selector: "bit-chip-select", templateUrl: "chip-select.component.html", diff --git a/libs/components/src/chip-select/chip-select.mdx b/libs/components/src/chip-select/chip-select.mdx index d569158b75a..b09b9664f8e 100644 --- a/libs/components/src/chip-select/chip-select.mdx +++ b/libs/components/src/chip-select/chip-select.mdx @@ -1,4 +1,4 @@ -import { Meta, Primary, Controls, Canvas } from "@storybook/addon-docs"; +import { Meta, Primary, Controls, Canvas, Title, Description } from "@storybook/addon-docs"; import * as stories from "./chip-select.stories"; @@ -8,9 +8,8 @@ import * as stories from "./chip-select.stories"; import { ChipSelectComponent } from "@bitwarden/components"; ``` -# Chip Select - -`<bit-chip-select>` is a select element that is commonly used to filter items in lists or tables. +<Title /> +<Description /> <Canvas of={stories.Default} /> diff --git a/libs/components/src/color-password/color-password.component.ts b/libs/components/src/color-password/color-password.component.ts index 2dd78e8525d..a6cd58044a3 100644 --- a/libs/components/src/color-password/color-password.component.ts +++ b/libs/components/src/color-password/color-password.component.ts @@ -10,7 +10,11 @@ enum CharacterType { Special, Number, } - +/** + * The color password is used primarily in the Generator pages and in the Login type form. It includes + * the logic for displaying letters as `text-main`, numbers as `primary`, and special symbols as + * `danger`. + */ @Component({ selector: "bit-color-password", template: `@for (character of passwordCharArray(); track $index; let i = $index) { diff --git a/libs/components/src/color-password/color-password.mdx b/libs/components/src/color-password/color-password.mdx index 8f3746715e1..4deeace9b9e 100644 --- a/libs/components/src/color-password/color-password.mdx +++ b/libs/components/src/color-password/color-password.mdx @@ -1,14 +1,15 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./color-password.stories"; <Meta of={stories} /> -# Color password +```ts +import { ColorPasswordModule } from "@bitwarden/components"; +``` -The color password is used primarily in the Generator pages and in the Login type form. It includes -the logic for displaying letters as `text-main`, numbers as `primary`, and special symbols as -`danger`. +<Title /> +<Description /> <Primary /> <Controls /> diff --git a/libs/components/src/color-password/color-password.stories.ts b/libs/components/src/color-password/color-password.stories.ts index bb835d97d4a..5a544dcb22e 100644 --- a/libs/components/src/color-password/color-password.stories.ts +++ b/libs/components/src/color-password/color-password.stories.ts @@ -1,5 +1,7 @@ import { Meta, StoryObj } from "@storybook/angular"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; + import { ColorPasswordComponent } from "./color-password.component"; const examplePassword = "Wq$Jk😀7j DX#rS5Sdi!z "; @@ -25,7 +27,7 @@ export const ColorPassword: Story = { render: (args) => ({ props: args, template: ` - <bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password> + <bit-color-password ${formatArgsForCodeSnippet<ColorPasswordComponent>(args)}></bit-color-password> `, }), }; @@ -35,7 +37,7 @@ export const WrappedColorPassword: Story = { props: args, template: ` <div class="tw-max-w-32"> - <bit-color-password class="tw-text-base" [password]="password" [showCount]="showCount"></bit-color-password> + <bit-color-password ${formatArgsForCodeSnippet<ColorPasswordComponent>(args)}></bit-color-password> </div> `, }), diff --git a/libs/components/src/disclosure/disclosure.component.ts b/libs/components/src/disclosure/disclosure.component.ts index 6de06b48b3f..c18a2e31ea6 100644 --- a/libs/components/src/disclosure/disclosure.component.ts +++ b/libs/components/src/disclosure/disclosure.component.ts @@ -11,6 +11,30 @@ import { let nextId = 0; +/** + * The `bit-disclosure` component is used in tandem with the `bitDisclosureTriggerFor` directive to create an accessible content area whose visibility is controlled by a trigger button. + + * To compose a disclosure and trigger: + + * 1. Create a trigger component (see "Supported Trigger Components" section below) + * 2. Create a `bit-disclosure` + * 3. Set a template reference on the `bit-disclosure` + * 4. Use the `bitDisclosureTriggerFor` directive on the trigger component, and pass it the `bit-disclosure` template reference + * 5. Set the `open` property on the `bit-disclosure` to init the disclosure as either currently expanded or currently collapsed. The disclosure will default to `false`, meaning it defaults to being hidden. + * + * @example + * + * ```html + * <button + * type="button" + * bitIconButton="bwi-sliders" + * [buttonType]="'muted'" + * [bitDisclosureTriggerFor]="disclosureRef" + * ></button> + * <bit-disclosure #disclosureRef open>click button to hide this content</bit-disclosure> + * ``` + * + */ @Component({ selector: "bit-disclosure", standalone: true, diff --git a/libs/components/src/disclosure/disclosure.mdx b/libs/components/src/disclosure/disclosure.mdx index 2fcff6f5982..50ccf936acc 100644 --- a/libs/components/src/disclosure/disclosure.mdx +++ b/libs/components/src/disclosure/disclosure.mdx @@ -1,4 +1,4 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./disclosure.stories"; @@ -8,37 +8,11 @@ import * as stories from "./disclosure.stories"; import { DisclosureComponent, DisclosureTriggerForDirective } from "@bitwarden/components"; ``` -# Disclosure - -The `bit-disclosure` component is used in tandem with the `bitDisclosureTriggerFor` directive to -create an accessible content area whose visibility is controlled by a trigger button. - -To compose a disclosure and trigger: - -1. Create a trigger component (see "Supported Trigger Components" section below) -2. Create a `bit-disclosure` -3. Set a template reference on the `bit-disclosure` -4. Use the `bitDisclosureTriggerFor` directive on the trigger component, and pass it the - `bit-disclosure` template reference -5. Set the `open` property on the `bit-disclosure` to init the disclosure as either currently - expanded or currently collapsed. The disclosure will default to `false`, meaning it defaults to - being hidden. - -``` -<button - type="button" - bitIconButton="bwi-sliders" - [buttonType]="'muted'" - [bitDisclosureTriggerFor]="disclosureRef" -></button> -<bit-disclosure #disclosureRef open>click button to hide this content</bit-disclosure> -``` +<Title /> +<Description /> <Canvas of={stories.DisclosureWithIconButton} /> -<br /> -<br /> - ## Supported Trigger Components This is the list of currently supported trigger components: diff --git a/libs/components/src/icon-button/icon-button.component.ts b/libs/components/src/icon-button/icon-button.component.ts index 60877070e2b..573708b1e40 100644 --- a/libs/components/src/icon-button/icon-button.component.ts +++ b/libs/components/src/icon-button/icon-button.component.ts @@ -147,7 +147,13 @@ const sizes: Record<IconButtonSize, string[]> = { default: ["tw-px-2.5", "tw-py-1.5"], small: ["tw-leading-none", "tw-text-base", "tw-p-1"], }; +/** + * Icon buttons are used when no text accompanies the button. It consists of an icon that may be updated to any icon in the `bwi-font`, a `title` attribute, and an `aria-label`. + * The most common use of the icon button is in the banner, toast, and modal components as a close button. It can also be found in tables as the 3 dot option menu, or on navigation list items when there are options that need to be collapsed into a menu. + + * Similar to the main button components, spacing between multiple icon buttons should be .5rem. + */ @Component({ selector: "button[bitIconButton]:not(button[bitButton])", templateUrl: "icon-button.component.html", diff --git a/libs/components/src/icon-button/icon-button.mdx b/libs/components/src/icon-button/icon-button.mdx index 85164717de7..637a9d7daa0 100644 --- a/libs/components/src/icon-button/icon-button.mdx +++ b/libs/components/src/icon-button/icon-button.mdx @@ -1,4 +1,4 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./icon-button.stories"; @@ -8,16 +8,8 @@ import * as stories from "./icon-button.stories"; import { IconButtonModule } from "@bitwarden/components"; ``` -# Icon Button - -Icon buttons are used when no text accompanies the button. It consists of an icon that may be -updated to any icon in the `bwi-font`, a `title` attribute, and an `aria-label`. - -The most common use of the icon button is in the banner, toast, and modal components as a close -button. It can also be found in tables as the 3 dot option menu, or on navigation list items when -there are options that need to be collapsed into a menu. - -Similar to the main button components, spacing between multiple icon buttons should be .5rem. +<Title /> +<Description /> <Primary /> <Controls /> diff --git a/libs/components/src/icon-button/icon-button.stories.ts b/libs/components/src/icon-button/icon-button.stories.ts index 08c95c5d641..f63c494f7db 100644 --- a/libs/components/src/icon-button/icon-button.stories.ts +++ b/libs/components/src/icon-button/icon-button.stories.ts @@ -1,5 +1,7 @@ import { Meta, StoryObj } from "@storybook/angular"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; + import { BitIconButtonComponent } from "./icon-button.component"; export default { @@ -7,8 +9,11 @@ export default { component: BitIconButtonComponent, args: { bitIconButton: "bwi-plus", - size: "default", - disabled: false, + }, + argTypes: { + buttonType: { + options: ["primary", "secondary", "danger", "unstyled", "contrast", "main", "muted", "light"], + }, }, parameters: { design: { @@ -24,25 +29,9 @@ export const Default: Story = { render: (args) => ({ props: args, template: /*html*/ ` - <div class="tw-space-x-4"> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="main" [size]="size">Button</button> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="muted" [size]="size">Button</button> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="primary" [size]="size">Button</button> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="secondary"[size]="size">Button</button> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="danger" [size]="size">Button</button> - <div class="tw-bg-primary-600 tw-p-2 tw-inline-block"> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="contrast" [size]="size">Button</button> - </div> - <div class="tw-bg-background-alt2 tw-p-2 tw-inline-block"> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" buttonType="light" [size]="size">Button</button> - </div> - </div> + <button ${formatArgsForCodeSnippet<BitIconButtonComponent>(args)}>Button</button> `, }), - args: { - size: "default", - buttonType: "primary", - }, }; export const Small: Story = { @@ -54,40 +43,35 @@ export const Small: Story = { }; export const Primary: Story = { - render: (args) => ({ - props: args, - template: /*html*/ ` - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size">Button</button> - `, - }), + ...Default, args: { buttonType: "primary", }, }; export const Secondary: Story = { - ...Primary, + ...Default, args: { buttonType: "secondary", }, }; export const Danger: Story = { - ...Primary, + ...Default, args: { buttonType: "danger", }, }; export const Main: Story = { - ...Primary, + ...Default, args: { buttonType: "main", }, }; export const Muted: Story = { - ...Primary, + ...Default, args: { buttonType: "muted", }, @@ -98,7 +82,8 @@ export const Light: Story = { props: args, template: /*html*/ ` <div class="tw-bg-background-alt2 tw-p-6 tw-w-full tw-inline-block"> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size">Button</button> + <!-- <div> used only to provide dark background color --> + <button ${formatArgsForCodeSnippet<BitIconButtonComponent>(args)}>Button</button> </div> `, }), @@ -112,7 +97,8 @@ export const Contrast: Story = { props: args, template: /*html*/ ` <div class="tw-bg-primary-600 tw-p-6 tw-w-full tw-inline-block"> - <button bitIconButton="bwi-plus" [disabled]="disabled" [loading]="loading" [buttonType]="buttonType" [size]="size">Button</button> + <!-- <div> used only to provide dark background color --> + <button ${formatArgsForCodeSnippet<BitIconButtonComponent>(args)}>Button</button> </div> `, }), diff --git a/libs/components/src/icon/icon.mdx b/libs/components/src/icon/icon.mdx index 6435fc24948..d1809c81cd2 100644 --- a/libs/components/src/icon/icon.mdx +++ b/libs/components/src/icon/icon.mdx @@ -4,6 +4,10 @@ import * as stories from "./icon.stories"; <Meta of={stories} /> +```ts +import { IconModule } from "@bitwarden/components"; +``` + # Icon Use Instructions - Icons will generally be attached to the associated Jira task. diff --git a/libs/components/src/link/link.directive.ts b/libs/components/src/link/link.directive.ts index 52aba557661..ca25e5fef56 100644 --- a/libs/components/src/link/link.directive.ts +++ b/libs/components/src/link/link.directive.ts @@ -66,6 +66,14 @@ abstract class LinkDirective { linkType: LinkType = "primary"; } +/** + * Text Links and Buttons can use either the `<a>` or `<button>` tags. Choose which based on the action the button takes: + + * - if navigating to a new page, use a `<a>` + * - if taking an action on the current page, use a `<button>` + + * Text buttons or links are most commonly used in paragraphs of text or in forms to customize actions or show/hide additional form options. + */ @Directive({ selector: "a[bitLink]", standalone: true, diff --git a/libs/components/src/link/link.mdx b/libs/components/src/link/link.mdx index e509ddb9911..8fb5f693f10 100644 --- a/libs/components/src/link/link.mdx +++ b/libs/components/src/link/link.mdx @@ -1,4 +1,4 @@ -import { Meta, Story, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Story, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./link.stories"; @@ -8,18 +8,11 @@ import * as stories from "./link.stories"; import { LinkModule } from "@bitwarden/components"; ``` -# Link / Text button - -Text Links and Buttons can use either the `<a>` or `<button>` tags. Choose which based on the action -the button takes: - -- if navigating to a new page, use a `<a>` -- if taking an action on the current page, use a `<button>` - -Text buttons or links are most commonly used in paragraphs of text or in forms to customize actions -or show/hide additional form options. +<Title>Link / Text button + + ## Variants diff --git a/libs/components/src/link/link.stories.ts b/libs/components/src/link/link.stories.ts index d07d33ae589..edf2cb14cd6 100644 --- a/libs/components/src/link/link.stories.ts +++ b/libs/components/src/link/link.stories.ts @@ -1,5 +1,7 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; + import { AnchorLinkDirective, ButtonLinkDirective } from "./link.directive"; import { LinkModule } from "./link.module"; @@ -27,6 +29,14 @@ export default { type Story = StoryObj; export const Default: Story = { + render: (args) => ({ + template: /*html*/ ` + (args)}>Your text here + `, + }), +}; + +export const InteractionStates: Story = { render: () => ({ template: /*html*/ `
diff --git a/libs/components/src/progress/progress.component.ts b/libs/components/src/progress/progress.component.ts index 04e535158b1..cc2a6df7340 100644 --- a/libs/components/src/progress/progress.component.ts +++ b/libs/components/src/progress/progress.component.ts @@ -1,22 +1,25 @@ import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; -type SizeTypes = "small" | "default" | "large"; -type BackgroundTypes = "danger" | "primary" | "success" | "warning"; +type ProgressSizeType = "small" | "default" | "large"; +type BackgroundType = "danger" | "primary" | "success" | "warning"; -const SizeClasses: Record = { +const SizeClasses: Record = { small: ["tw-h-1"], default: ["tw-h-4"], large: ["tw-h-6"], }; -const BackgroundClasses: Record = { +const BackgroundClasses: Record = { danger: ["tw-bg-danger-600"], primary: ["tw-bg-primary-600"], success: ["tw-bg-success-600"], warning: ["tw-bg-warning-600"], }; +/** + * Progress indicators may be used to visually indicate progress or to visually measure some other value, such as a password strength indicator. + */ @Component({ selector: "bit-progress", templateUrl: "./progress.component.html", @@ -25,9 +28,9 @@ const BackgroundClasses: Record = { }) export class ProgressComponent { @Input() barWidth = 0; - @Input() bgColor: BackgroundTypes = "primary"; + @Input() bgColor: BackgroundType = "primary"; @Input() showText = true; - @Input() size: SizeTypes = "default"; + @Input() size: ProgressSizeType = "default"; @Input() text?: string; get displayText() { diff --git a/libs/components/src/progress/progress.mdx b/libs/components/src/progress/progress.mdx index 9a75f8ae1fa..def2f239129 100644 --- a/libs/components/src/progress/progress.mdx +++ b/libs/components/src/progress/progress.mdx @@ -1,13 +1,15 @@ -import { Meta, Canvas, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./progress.stories"; -# Progress +```ts +import { ProgressModule } from "@bitwarden/components"; +``` -Progress indicators may be used to visually indicate progress or to visually measure some other -value, such as a password strength indicator. + +<Description /> <Primary /> <Controls /> diff --git a/libs/components/src/progress/progress.stories.ts b/libs/components/src/progress/progress.stories.ts index 1484dab0a21..5c7eb066cd3 100644 --- a/libs/components/src/progress/progress.stories.ts +++ b/libs/components/src/progress/progress.stories.ts @@ -1,5 +1,7 @@ import { Meta, StoryObj } from "@storybook/angular"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; + import { ProgressComponent } from "./progress.component"; export default { @@ -20,19 +22,34 @@ export default { type Story = StoryObj<ProgressComponent>; +export const Base: Story = { + render: (args) => ({ + props: args, + template: ` + <bit-progress ${formatArgsForCodeSnippet<ProgressComponent>(args)}></bit-progress> + `, + }), + args: { + barWidth: 50, + }, +}; + export const Empty: Story = { + ...Base, args: { barWidth: 0, }, }; export const Full: Story = { + ...Base, args: { barWidth: 100, }, }; export const CustomText: Story = { + ...Base, args: { barWidth: 25, text: "Loading...", diff --git a/libs/components/src/search/search.mdx b/libs/components/src/search/search.mdx index 492fd0dda2d..7775225b8c2 100644 --- a/libs/components/src/search/search.mdx +++ b/libs/components/src/search/search.mdx @@ -1,4 +1,4 @@ -import { Meta, Canvas, Source, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Source, Primary, Controls, Title } from "@storybook/addon-docs"; import * as stories from "./search.stories"; @@ -8,7 +8,7 @@ import * as stories from "./search.stories"; import { SearchModule } from "@bitwarden/components"; ``` -# Search +<Title>Search field diff --git a/libs/components/src/search/search.stories.ts b/libs/components/src/search/search.stories.ts index a6cd714d43a..526e1381d70 100644 --- a/libs/components/src/search/search.stories.ts +++ b/libs/components/src/search/search.stories.ts @@ -3,6 +3,7 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; import { InputModule } from "../input/input.module"; import { SharedModule } from "../shared"; import { I18nMockService } from "../utils/i18n-mock.service"; @@ -27,6 +28,10 @@ export default { ], }), ], + args: { + placeholder: "search", + disabled: false, + }, } as Meta; type Story = StoryObj; @@ -35,7 +40,7 @@ export const Default: Story = { render: (args) => ({ props: args, template: ` - + (args)}> `, }), args: {}, diff --git a/libs/components/src/toast/toast.mdx b/libs/components/src/toast/toast.mdx index d27109b4772..6d9d80c6ae5 100644 --- a/libs/components/src/toast/toast.mdx +++ b/libs/components/src/toast/toast.mdx @@ -1,4 +1,4 @@ -import { Meta, Canvas, Source, Primary, Controls } from "@storybook/addon-docs"; +import { Meta, Canvas, Source, Primary, Controls, Title, Description } from "@storybook/addon-docs"; import * as stories from "./toast.stories"; @@ -8,12 +8,16 @@ import * as stories from "./toast.stories"; import { ToastService } from "@bitwarden/components"; ``` -# Toast + -Toasts are ephemeral notifications. They most often communicate the result of a user action. Due to -their ephemeral nature, long messages and critical alerts should not utilize toasts. +<Primary /> +<Controls /> -<Canvas of={stories.Default} /> +### Variants + +<Canvas of={stories.Variants} /> + +### Long content <Canvas of={stories.LongContent} /> @@ -38,7 +42,7 @@ The following options are accepted: <Canvas of={stories.Service} /> -## Toast container +### Toast container `bit-toast-container` should be added to the app root of consuming clients to ensure toasts are properly announced to screenreaders. @@ -48,7 +52,7 @@ properly announced to screenreaders. <bit-toast-container></bit-toast-container> ``` -## Accessibility +### Accessibility In addition to the accessibility provided by the `bit-toast-container` component, the toast itself will apply `aria-alert="true"` if the toast is of type `error`. diff --git a/libs/components/src/toast/toast.stories.ts b/libs/components/src/toast/toast.stories.ts index 0af4974eead..b4a80cd3276 100644 --- a/libs/components/src/toast/toast.stories.ts +++ b/libs/components/src/toast/toast.stories.ts @@ -6,6 +6,7 @@ import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/an import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; import { ButtonModule } from "../button"; import { I18nMockService } from "../utils/i18n-mock.service"; @@ -75,11 +76,22 @@ export const Default: Story = { render: (args) => ({ props: args, template: ` - <div class="tw-flex tw-flex-col tw-min-w tw-max-w-[--bit-toast-width]"> - <bit-toast [title]="title" [message]="message" [progressWidth]="progressWidth" (onClose)="onClose()" variant="success"></bit-toast> - <bit-toast [title]="title" [message]="message" [progressWidth]="progressWidth" (onClose)="onClose()" variant="info"></bit-toast> - <bit-toast [title]="title" [message]="message" [progressWidth]="progressWidth" (onClose)="onClose()" variant="warning"></bit-toast> - <bit-toast [title]="title" [message]="message" [progressWidth]="progressWidth" (onClose)="onClose()" variant="error"></bit-toast> + <div class="tw-min-w tw-max-w-[--bit-toast-width]"> + <bit-toast ${formatArgsForCodeSnippet<ToastComponent>(args)}></bit-toast> + </div> + `, + }), +}; + +export const Variants: Story = { + render: (args) => ({ + props: args, + template: ` + <div class="tw-flex tw-flex-col tw-min-w tw-max-w-[--bit-toast-width] tw-gap-2"> + <bit-toast ${formatArgsForCodeSnippet<ToastComponent>(args)} variant="success"></bit-toast> + <bit-toast ${formatArgsForCodeSnippet<ToastComponent>(args)} variant="info"></bit-toast> + <bit-toast ${formatArgsForCodeSnippet<ToastComponent>(args)} variant="warning"></bit-toast> + <bit-toast ${formatArgsForCodeSnippet<ToastComponent>(args)} variant="error"></bit-toast> </div> `, }), @@ -93,8 +105,8 @@ export const LongContent: Story = { args: { title: "Foo", message: [ - "Lorem ipsum dolor sit amet, consectetur adipisci", - "Lorem ipsum dolor sit amet, consectetur adipisci", + "Maecenas commodo posuere quam, vel malesuada nulla accumsan ac.", + "Pellentesque interdum ligula ante, eget bibendum ante lacinia congue.", ], }, }; diff --git a/libs/components/src/toast/toastr.component.ts b/libs/components/src/toast/toastr.component.ts index 75124ceb4b3..06182f094aa 100644 --- a/libs/components/src/toast/toastr.component.ts +++ b/libs/components/src/toast/toastr.component.ts @@ -4,6 +4,9 @@ import { Toast as BaseToastrComponent, ToastPackage, ToastrService } from "ngx-t import { ToastComponent } from "./toast.component"; +/** + * Toasts are ephemeral notifications. They most often communicate the result of a user action. Due to their ephemeral nature, long messages and critical alerts should not utilize toasts. + */ @Component({ template: ` <bit-toast diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 90b95ff54bf..a60a7053182 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -46,6 +46,7 @@ ".storybook/main.ts", ".storybook/manager.js", ".storybook/test-runner.ts", + ".storybook/format-args-for-code-snippet.ts", "apps/browser/src/autofill/content/components/.lit-storybook/main.ts" ], "include": ["apps/**/*", "libs/**/*", "bitwarden_license/**/*", "scripts/**/*"], From 895d54fd5eb92a520d76fe8458ef94fc2765890e Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Fri, 30 May 2025 11:40:55 -0500 Subject: [PATCH 197/245] [PM-21443] Require userId for KeyService's everHadUserKey$ (#14712) * Require userId for KeyService's everHadUserKey$ * handle null active user in tdeDecryptionRequiredGuard --- .../src/auth/guards/lock.guard.spec.ts | 2 +- libs/angular/src/auth/guards/lock.guard.ts | 2 +- .../angular/src/auth/guards/redirect.guard.ts | 6 +- .../tde-decryption-required.guard.spec.ts | 107 ++++++++++++++++++ .../guards/tde-decryption-required.guard.ts | 11 +- .../src/auth/guards/unauth.guard.spec.ts | 4 +- libs/angular/src/auth/guards/unauth.guard.ts | 2 +- .../src/abstractions/key.service.ts | 6 +- libs/key-management/src/key.service.spec.ts | 11 +- libs/key-management/src/key.service.ts | 16 ++- 10 files changed, 142 insertions(+), 25 deletions(-) create mode 100644 libs/angular/src/auth/guards/tde-decryption-required.guard.spec.ts diff --git a/libs/angular/src/auth/guards/lock.guard.spec.ts b/libs/angular/src/auth/guards/lock.guard.spec.ts index 32b8ecbb9dd..ed77f9bdebf 100644 --- a/libs/angular/src/auth/guards/lock.guard.spec.ts +++ b/libs/angular/src/auth/guards/lock.guard.spec.ts @@ -44,7 +44,7 @@ describe("lockGuard", () => { const keyService: MockProxy<KeyService> = mock<KeyService>(); keyService.isLegacyUser.mockResolvedValue(setupParams.isLegacyUser); - keyService.everHadUserKey$ = of(setupParams.everHadUserKey); + keyService.everHadUserKey$.mockReturnValue(of(setupParams.everHadUserKey)); const platformUtilService: MockProxy<PlatformUtilsService> = mock<PlatformUtilsService>(); platformUtilService.getClientType.mockReturnValue(setupParams.clientType); diff --git a/libs/angular/src/auth/guards/lock.guard.ts b/libs/angular/src/auth/guards/lock.guard.ts index 10ad4917f32..01d03dc718d 100644 --- a/libs/angular/src/auth/guards/lock.guard.ts +++ b/libs/angular/src/auth/guards/lock.guard.ts @@ -84,7 +84,7 @@ export function lockGuard(): CanActivateFn { } // If authN user with TDE directly navigates to lock, reject that navigation - const everHadUserKey = await firstValueFrom(keyService.everHadUserKey$); + const everHadUserKey = await firstValueFrom(keyService.everHadUserKey$(activeUser.id)); if (tdeEnabled && !everHadUserKey) { return false; } diff --git a/libs/angular/src/auth/guards/redirect.guard.ts b/libs/angular/src/auth/guards/redirect.guard.ts index 00dd20c9909..b893614b405 100644 --- a/libs/angular/src/auth/guards/redirect.guard.ts +++ b/libs/angular/src/auth/guards/redirect.guard.ts @@ -2,8 +2,10 @@ import { inject } from "@angular/core"; import { CanActivateFn, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { KeyService } from "@bitwarden/key-management"; @@ -33,6 +35,7 @@ export function redirectGuard(overrides: Partial<RedirectRoutes> = {}): CanActiv const authService = inject(AuthService); const keyService = inject(KeyService); const deviceTrustService = inject(DeviceTrustServiceAbstraction); + const accountService = inject(AccountService); const logService = inject(LogService); const router = inject(Router); @@ -49,7 +52,8 @@ export function redirectGuard(overrides: Partial<RedirectRoutes> = {}): CanActiv // If locked, TDE is enabled, and the user hasn't decrypted yet, then redirect to the // login decryption options component. const tdeEnabled = await firstValueFrom(deviceTrustService.supportsDeviceTrust$); - const everHadUserKey = await firstValueFrom(keyService.everHadUserKey$); + const userId = await firstValueFrom(accountService.activeAccount$.pipe(getUserId)); + const everHadUserKey = await firstValueFrom(keyService.everHadUserKey$(userId)); if (authStatus === AuthenticationStatus.Locked && tdeEnabled && !everHadUserKey) { logService.info( "Sending user to TDE decryption options. AuthStatus is %s. TDE support is %s. Ever had user key is %s.", diff --git a/libs/angular/src/auth/guards/tde-decryption-required.guard.spec.ts b/libs/angular/src/auth/guards/tde-decryption-required.guard.spec.ts new file mode 100644 index 00000000000..4408452a2a2 --- /dev/null +++ b/libs/angular/src/auth/guards/tde-decryption-required.guard.spec.ts @@ -0,0 +1,107 @@ +import { TestBed } from "@angular/core/testing"; +import { Router, provideRouter } from "@angular/router"; +import { mock } from "jest-mock-extended"; +import { BehaviorSubject, of } from "rxjs"; + +import { EmptyComponent } from "@bitwarden/angular/platform/guard/feature-flag.guard.spec"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { KeyService } from "@bitwarden/key-management"; + +import { tdeDecryptionRequiredGuard } from "./tde-decryption-required.guard"; + +describe("tdeDecryptionRequiredGuard", () => { + const activeUser: Account = { + id: "fake_user_id" as UserId, + email: "test@email.com", + emailVerified: true, + name: "Test User", + }; + + const setup = ( + activeUser: Account | null, + authStatus: AuthenticationStatus | null = null, + tdeEnabled: boolean = false, + everHadUserKey: boolean = false, + ) => { + const accountService = mock<AccountService>(); + const authService = mock<AuthService>(); + const keyService = mock<KeyService>(); + const deviceTrustService = mock<DeviceTrustServiceAbstraction>(); + const logService = mock<LogService>(); + + accountService.activeAccount$ = new BehaviorSubject<Account | null>(activeUser); + if (authStatus !== null) { + authService.getAuthStatus.mockResolvedValue(authStatus); + } + keyService.everHadUserKey$.mockReturnValue(of(everHadUserKey)); + deviceTrustService.supportsDeviceTrust$ = of(tdeEnabled); + + const testBed = TestBed.configureTestingModule({ + providers: [ + { provide: AccountService, useValue: accountService }, + { provide: AuthService, useValue: authService }, + { provide: KeyService, useValue: keyService }, + { provide: DeviceTrustServiceAbstraction, useValue: deviceTrustService }, + { provide: LogService, useValue: logService }, + provideRouter([ + { path: "", component: EmptyComponent }, + { + path: "protected-route", + component: EmptyComponent, + canActivate: [tdeDecryptionRequiredGuard()], + }, + ]), + ], + }); + + return { + router: testBed.inject(Router), + }; + }; + + it("redirects to root when the active account is null", async () => { + const { router } = setup(null, null); + await router.navigate(["protected-route"]); + expect(router.url).toBe("/"); + }); + + test.each([AuthenticationStatus.Unlocked, AuthenticationStatus.LoggedOut])( + "redirects to root when the user isn't locked", + async (authStatus) => { + const { router } = setup(activeUser, authStatus); + + await router.navigate(["protected-route"]); + + expect(router.url).toBe("/"); + }, + ); + + it("redirects to root when TDE is not enabled", async () => { + const { router } = setup(activeUser, AuthenticationStatus.Locked, false, true); + + await router.navigate(["protected-route"]); + + expect(router.url).toBe("/"); + }); + + it("redirects to root when user has had a user key", async () => { + const { router } = setup(activeUser, AuthenticationStatus.Locked, true, true); + + await router.navigate(["protected-route"]); + + expect(router.url).toBe("/"); + }); + + it("allows access when user is locked, TDE is enabled, and user has never had a user key", async () => { + const { router } = setup(activeUser, AuthenticationStatus.Locked, true, false); + + const result = await router.navigate(["protected-route"]); + expect(result).toBe(true); + expect(router.url).toBe("/protected-route"); + }); +}); diff --git a/libs/angular/src/auth/guards/tde-decryption-required.guard.ts b/libs/angular/src/auth/guards/tde-decryption-required.guard.ts index 1d98b1fa740..13e7c6d04e1 100644 --- a/libs/angular/src/auth/guards/tde-decryption-required.guard.ts +++ b/libs/angular/src/auth/guards/tde-decryption-required.guard.ts @@ -5,8 +5,9 @@ import { RouterStateSnapshot, CanActivateFn, } from "@angular/router"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; @@ -24,12 +25,18 @@ export function tdeDecryptionRequiredGuard(): CanActivateFn { const authService = inject(AuthService); const keyService = inject(KeyService); const deviceTrustService = inject(DeviceTrustServiceAbstraction); + const accountService = inject(AccountService); const logService = inject(LogService); const router = inject(Router); + const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id))); + if (userId == null) { + return router.createUrlTree(["/"]); + } + const authStatus = await authService.getAuthStatus(); const tdeEnabled = await firstValueFrom(deviceTrustService.supportsDeviceTrust$); - const everHadUserKey = await firstValueFrom(keyService.everHadUserKey$); + const everHadUserKey = await firstValueFrom(keyService.everHadUserKey$(userId)); // We need to determine if we should bypass the decryption options and send the user to the vault. // The ONLY time that we want to send a user to the decryption options is when: diff --git a/libs/angular/src/auth/guards/unauth.guard.spec.ts b/libs/angular/src/auth/guards/unauth.guard.spec.ts index ad0ce680a1f..c696b849558 100644 --- a/libs/angular/src/auth/guards/unauth.guard.spec.ts +++ b/libs/angular/src/auth/guards/unauth.guard.spec.ts @@ -2,7 +2,7 @@ import { TestBed } from "@angular/core/testing"; import { Router } from "@angular/router"; import { RouterTestingModule } from "@angular/router/testing"; import { MockProxy, mock } from "jest-mock-extended"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { EmptyComponent } from "@bitwarden/angular/platform/guard/feature-flag.guard.spec"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -43,7 +43,7 @@ describe("UnauthGuard", () => { authService.authStatusFor$.mockReturnValue(activeAccountStatusObservable); } - keyService.everHadUserKey$ = new BehaviorSubject<boolean>(everHadUserKey); + keyService.everHadUserKey$.mockReturnValue(of(everHadUserKey)); deviceTrustService.supportsDeviceTrustByUserId$.mockReturnValue( new BehaviorSubject<boolean>(tdeEnabled), ); diff --git a/libs/angular/src/auth/guards/unauth.guard.ts b/libs/angular/src/auth/guards/unauth.guard.ts index 6764b46843e..3fcfd38349b 100644 --- a/libs/angular/src/auth/guards/unauth.guard.ts +++ b/libs/angular/src/auth/guards/unauth.guard.ts @@ -50,7 +50,7 @@ async function unauthGuard( const tdeEnabled = await firstValueFrom( deviceTrustService.supportsDeviceTrustByUserId$(activeUser.id), ); - const everHadUserKey = await firstValueFrom(keyService.everHadUserKey$); + const everHadUserKey = await firstValueFrom(keyService.everHadUserKey$(activeUser.id)); // If locked, TDE is enabled, and the user hasn't decrypted yet, then redirect to the // login decryption options component. diff --git a/libs/key-management/src/abstractions/key.service.ts b/libs/key-management/src/abstractions/key.service.ts index 95b79890c6a..51a99421967 100644 --- a/libs/key-management/src/abstractions/key.service.ts +++ b/libs/key-management/src/abstractions/key.service.ts @@ -85,11 +85,13 @@ export abstract class KeyService { * (such as auto, biometrics, or pin) */ abstract refreshAdditionalKeys(): Promise<void>; + /** - * Observable value that returns whether or not the currently active user has ever had auser key, + * Observable value that returns whether or not the user has ever had a userKey, * i.e. has ever been unlocked/decrypted. This is key for differentiating between TDE locked and standard locked states. */ - abstract everHadUserKey$: Observable<boolean>; + abstract everHadUserKey$(userId: UserId): Observable<boolean>; + /** * Retrieves the user key * @param userId The desired user diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index 6d2e8fd20ec..400d7279a30 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -34,7 +34,6 @@ import { FakeAccountService, mockAccountServiceWith, FakeStateProvider, - FakeActiveUserState, FakeSingleUserState, } from "@bitwarden/common/spec"; import { CsprngArray } from "@bitwarden/common/types/csprng"; @@ -190,28 +189,28 @@ describe("keyService", () => { }); describe("everHadUserKey$", () => { - let everHadUserKeyState: FakeActiveUserState<boolean>; + let everHadUserKeyState: FakeSingleUserState<boolean>; beforeEach(() => { - everHadUserKeyState = stateProvider.activeUser.getFake(USER_EVER_HAD_USER_KEY); + everHadUserKeyState = stateProvider.singleUser.getFake(mockUserId, USER_EVER_HAD_USER_KEY); }); it("should return true when stored value is true", async () => { everHadUserKeyState.nextState(true); - expect(await firstValueFrom(keyService.everHadUserKey$)).toBe(true); + expect(await firstValueFrom(keyService.everHadUserKey$(mockUserId))).toBe(true); }); it("should return false when stored value is false", async () => { everHadUserKeyState.nextState(false); - expect(await firstValueFrom(keyService.everHadUserKey$)).toBe(false); + expect(await firstValueFrom(keyService.everHadUserKey$(mockUserId))).toBe(false); }); it("should return false when stored value is null", async () => { everHadUserKeyState.nextState(null); - expect(await firstValueFrom(keyService.everHadUserKey$)).toBe(false); + expect(await firstValueFrom(keyService.everHadUserKey$(mockUserId))).toBe(false); }); }); diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index 1d4fcc86a0c..fe288adeb88 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -41,7 +41,7 @@ import { USER_EVER_HAD_USER_KEY, USER_KEY, } from "@bitwarden/common/platform/services/key-state/user-key.state"; -import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state"; +import { StateProvider } from "@bitwarden/common/platform/state"; import { CsprngArray } from "@bitwarden/common/types/csprng"; import { OrganizationId, ProviderId, UserId } from "@bitwarden/common/types/guid"; import { @@ -63,10 +63,6 @@ import { import { KdfConfig } from "./models/kdf-config"; export class DefaultKeyService implements KeyServiceAbstraction { - private readonly activeUserEverHadUserKey: ActiveUserState<boolean>; - - readonly everHadUserKey$: Observable<boolean>; - readonly activeUserOrgKeys$: Observable<Record<OrganizationId, OrgKey>>; constructor( @@ -82,10 +78,6 @@ export class DefaultKeyService implements KeyServiceAbstraction { protected stateProvider: StateProvider, protected kdfConfigService: KdfConfigService, ) { - // User Key - this.activeUserEverHadUserKey = stateProvider.getActive(USER_EVER_HAD_USER_KEY); - this.everHadUserKey$ = this.activeUserEverHadUserKey.state$.pipe(map((x) => x ?? false)); - this.activeUserOrgKeys$ = this.stateProvider.activeUserId$.pipe( switchMap((userId) => (userId != null ? this.orgKeys$(userId) : NEVER)), ) as Observable<Record<OrganizationId, OrgKey>>; @@ -141,6 +133,12 @@ export class DefaultKeyService implements KeyServiceAbstraction { await this.setUserKey(key, activeUserId); } + everHadUserKey$(userId: UserId): Observable<boolean> { + return this.stateProvider + .getUser(userId, USER_EVER_HAD_USER_KEY) + .state$.pipe(map((x) => x ?? false)); + } + getInMemoryUserKeyFor$(userId: UserId): Observable<UserKey> { return this.stateProvider.getUserState$(USER_KEY, userId); } From 874fe0fd1ebde3392695183edba294a25451a0d8 Mon Sep 17 00:00:00 2001 From: Tom <144813356+ttalty@users.noreply.github.com> Date: Fri, 30 May 2025 12:55:14 -0400 Subject: [PATCH 198/245] Adding userGuid to the member details object (#14899) --- .../dirt/reports/risk-insights/models/password-health.ts | 1 + .../response/member-cipher-details.response.ts | 2 ++ .../risk-insights/services/risk-insights-report.service.ts | 6 +++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts index d24d8386ecd..acb4a116b8f 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts @@ -97,6 +97,7 @@ export type ExposedPasswordDetail = { * organization member to a cipher */ export type MemberDetailsFlat = { + userGuid: string; userName: string; email: string; cipherId: string; diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/response/member-cipher-details.response.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/response/member-cipher-details.response.ts index fcf5ada4b2c..7aa52330663 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/response/member-cipher-details.response.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/response/member-cipher-details.response.ts @@ -1,6 +1,7 @@ import { BaseResponse } from "@bitwarden/common/models/response/base.response"; export class MemberCipherDetailsResponse extends BaseResponse { + userGuid: string; userName: string; email: string; useKeyConnector: boolean; @@ -8,6 +9,7 @@ export class MemberCipherDetailsResponse extends BaseResponse { constructor(response: any) { super(response); + this.userGuid = this.getResponseProperty("UserGuid"); this.userName = this.getResponseProperty("UserName"); this.email = this.getResponseProperty("Email"); this.useKeyConnector = this.getResponseProperty("UseKeyConnector"); diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts index 6fdab58115d..afd246e1836 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts @@ -48,7 +48,9 @@ export class RiskInsightsReportService { const results$ = zip(allCiphers$, memberCiphers$).pipe( map(([allCiphers, memberCiphers]) => { const details: MemberDetailsFlat[] = memberCiphers.flatMap((dtl) => - dtl.cipherIds.map((c) => this.getMemberDetailsFlat(dtl.userName, dtl.email, c)), + dtl.cipherIds.map((c) => + this.getMemberDetailsFlat(dtl.userGuid, dtl.userName, dtl.email, c), + ), ); return [allCiphers, details] as const; }), @@ -408,11 +410,13 @@ export class RiskInsightsReportService { } private getMemberDetailsFlat( + userGuid: string, userName: string, email: string, cipherId: string, ): MemberDetailsFlat { return { + userGuid: userGuid, userName: userName, email: email, cipherId: cipherId, From 4e112e2daaba5141060f8d630e7ac232d722127a Mon Sep 17 00:00:00 2001 From: tangowithfoxtrot <5676771+tangowithfoxtrot@users.noreply.github.com> Date: Fri, 30 May 2025 10:30:08 -0700 Subject: [PATCH 199/245] feat: enable running as non-root user (#13887) --- apps/web/entrypoint.sh | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/apps/web/entrypoint.sh b/apps/web/entrypoint.sh index 16d1c78fb77..53e8af235fb 100644 --- a/apps/web/entrypoint.sh +++ b/apps/web/entrypoint.sh @@ -19,20 +19,29 @@ then LGID=65534 fi -# Create user and group +if [ "$(id -u)" = "0" ]; then + # Create user and group -groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 || -groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1 -useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 || -usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 -mkhomedir_helper $USERNAME + groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 || + groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1 + useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 || + usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 + mkhomedir_helper $USERNAME -# The rest... + # The rest... -chown -R $USERNAME:$GROUPNAME /etc/bitwarden -cp /etc/bitwarden/web/app-id.json /app/app-id.json -chown -R $USERNAME:$GROUPNAME /app -chown -R $USERNAME:$GROUPNAME /bitwarden_server + chown -R $USERNAME:$GROUPNAME /etc/bitwarden + chown -R $USERNAME:$GROUPNAME /app + chown -R $USERNAME:$GROUPNAME /bitwarden_server -exec gosu $USERNAME:$GROUPNAME dotnet /bitwarden_server/Server.dll \ - /contentRoot=/app /webRoot=. /serveUnknown=false /webVault=true + gosu_cmd="gosu $USERNAME:$GROUPNAME" +else + gosu_cmd="" +fi + +exec $gosu_cmd /bitwarden_server/Server \ + /contentRoot=/app \ + /webRoot=. \ + /serveUnknown=false \ + /webVault=true \ + /appIdLocation=/etc/bitwarden/web/app-id.json From 9f9cb0d13d0bc88c24e3adcac0b864a0ec336fc3 Mon Sep 17 00:00:00 2001 From: Matt Gibson <mgibson@bitwarden.com> Date: Fri, 30 May 2025 10:50:54 -0700 Subject: [PATCH 200/245] Add-userid-to-encryption-methods (#14844) * Get userId from response if available This is a small improvement for the Auth team which avoids inspection of the access token, sometimes. * Initialize sdk clients with a userId * return both Cipher and encryptedFor when encrypting a cipher Update cipher api requests to include encryptedFor attribute * Prefer named types with documentation * Update sdk to latest * Fixup types * Fixup tests * Revert getting userId from identity token response --------- Co-authored-by: Shane <smelton@bitwarden.com> --- .../notification.background.spec.ts | 17 +- .../background/notification.background.ts | 5 +- .../autofill/popup/fido2/fido2.component.ts | 6 +- .../vault-popup-autofill.service.spec.ts | 2 +- .../vault/components/add-edit.component.ts | 14 +- .../fido2/fido2-authenticator.service.spec.ts | 15 +- .../services/sdk/default-sdk.service.ts | 8 +- .../src/vault/abstractions/cipher.service.ts | 15 +- .../src/vault/models/data/field.data.ts | 2 +- .../request/cipher-bulk-share.request.ts | 9 +- .../models/request/cipher-create.request.ts | 6 +- .../models/request/cipher-share.request.ts | 6 +- .../models/request/cipher-with-id.request.ts | 6 +- .../vault/models/request/cipher.request.ts | 7 +- .../src/vault/models/view/cipher.view.ts | 2 +- .../src/vault/services/cipher.service.spec.ts | 168 +++++++++--------- .../src/vault/services/cipher.service.ts | 140 ++++++--------- .../services/default-cipher-form.service.ts | 9 +- .../assign-collections.component.ts | 2 +- 19 files changed, 212 insertions(+), 227 deletions(-) diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 009efd7ff36..b161200215a 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -69,8 +69,9 @@ describe("NotificationBackground", () => { const accountService = mock<AccountService>(); const organizationService = mock<OrganizationService>(); + const userId = "testId" as UserId; const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ - id: "testId" as UserId, + id: userId, email: "test@example.com", emailVerified: true, name: "Test User", @@ -1141,8 +1142,11 @@ describe("NotificationBackground", () => { convertAddLoginQueueMessageToCipherViewSpy.mockReturnValueOnce(cipherView); editItemSpy.mockResolvedValueOnce(undefined); cipherEncryptSpy.mockResolvedValueOnce({ - ...cipherView, - id: "testId", + cipher: { + ...cipherView, + id: "testId", + }, + encryptedFor: userId, }); sendMockExtensionMessage(message, sender); @@ -1188,6 +1192,13 @@ describe("NotificationBackground", () => { folderExistsSpy.mockResolvedValueOnce(true); convertAddLoginQueueMessageToCipherViewSpy.mockReturnValueOnce(cipherView); editItemSpy.mockResolvedValueOnce(undefined); + cipherEncryptSpy.mockResolvedValueOnce({ + cipher: { + ...cipherView, + id: "testId", + }, + encryptedFor: userId, + }); const errorMessage = "fetch error"; createWithServerSpy.mockImplementation(() => { throw new Error(errorMessage); diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index a73141b7e4d..cb6a67c8137 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -719,9 +719,10 @@ export default class NotificationBackground { return; } - const cipher = await this.cipherService.encrypt(newCipher, activeUserId); + const encrypted = await this.cipherService.encrypt(newCipher, activeUserId); + const { cipher } = encrypted; try { - await this.cipherService.createWithServer(cipher); + await this.cipherService.createWithServer(encrypted); await BrowserApi.tabSendMessageData(tab, "saveCipherAttemptCompleted", { itemName: newCipher?.name && String(newCipher?.name), cipherId: cipher?.id && String(cipher?.id), diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index 6b7d9120195..996d1bb6176 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -442,10 +442,10 @@ export class Fido2Component implements OnInit, OnDestroy { ); this.buildCipher(name, username); - const cipher = await this.cipherService.encrypt(this.cipher, activeUserId); + const encrypted = await this.cipherService.encrypt(this.cipher, activeUserId); try { - await this.cipherService.createWithServer(cipher); - this.cipher.id = cipher.id; + await this.cipherService.createWithServer(encrypted); + this.cipher.id = encrypted.cipher.id; } catch (e) { this.logService.error(e); } diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts index 415aeb31081..73c3fed3276 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts @@ -353,7 +353,7 @@ describe("VaultPopupAutofillService", () => { }); it("should add a URI to the cipher and save with the server", async () => { - const mockEncryptedCipher = {} as Cipher; + const mockEncryptedCipher = { cipher: {} as Cipher, encryptedFor: mockUserId }; mockCipherService.encrypt.mockResolvedValue(mockEncryptedCipher); const result = await service.doAutofillAndSave(mockCipher); expect(result).toBe(true); diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 8175372cae5..8cc79a22dfd 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -26,11 +26,13 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CollectionId, UserId } from "@bitwarden/common/types/guid"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { + CipherService, + EncryptionContext, +} from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType, SecureNoteType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; -import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CardView } from "@bitwarden/common/vault/models/view/card.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; @@ -740,17 +742,17 @@ export class AddEditComponent implements OnInit, OnDestroy { return this.cipherService.encrypt(this.cipher, userId); } - protected saveCipher(cipher: Cipher) { + protected saveCipher(data: EncryptionContext) { let orgAdmin = this.organization?.canEditAllCiphers; // if a cipher is unassigned we want to check if they are an admin or have permission to edit any collection - if (!cipher.collectionIds) { + if (!data.cipher.collectionIds) { orgAdmin = this.organization?.canEditUnassignedCiphers; } return this.cipher.id == null - ? this.cipherService.createWithServer(cipher, orgAdmin) - : this.cipherService.updateWithServer(cipher, orgAdmin); + ? this.cipherService.createWithServer(data, orgAdmin) + : this.cipherService.updateWithServer(data, orgAdmin); } protected deleteCipher(userId: UserId) { diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts index 5c377e1a980..78ae8253ee2 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts @@ -6,7 +6,7 @@ import { BehaviorSubject, of } from "rxjs"; import { mockAccountServiceWith } from "../../../../spec"; import { Account } from "../../../auth/abstractions/account.service"; import { UserId } from "../../../types/guid"; -import { CipherService } from "../../../vault/abstractions/cipher.service"; +import { CipherService, EncryptionContext } from "../../../vault/abstractions/cipher.service"; import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction"; import { CipherRepromptType } from "../../../vault/enums/cipher-reprompt-type"; import { CipherType } from "../../../vault/enums/cipher-type"; @@ -36,8 +36,9 @@ type ParentWindowReference = string; const RpId = "bitwarden.com"; describe("FidoAuthenticatorService", () => { + const userId = "testId" as UserId; const activeAccountSubject = new BehaviorSubject<Account | null>({ - id: "testId" as UserId, + id: userId, email: "test@example.com", emailVerified: true, name: "Test User", @@ -254,7 +255,7 @@ describe("FidoAuthenticatorService", () => { cipherId: existingCipher.id, userVerified: false, }); - cipherService.encrypt.mockResolvedValue(encryptedCipher as unknown as Cipher); + cipherService.encrypt.mockResolvedValue(encryptedCipher as unknown as EncryptionContext); await authenticator.makeCredential(params, windowReference); @@ -325,7 +326,7 @@ describe("FidoAuthenticatorService", () => { cipherId: existingCipher.id, userVerified: false, }); - cipherService.encrypt.mockResolvedValue(encryptedCipher as unknown as Cipher); + cipherService.encrypt.mockResolvedValue(encryptedCipher as unknown as EncryptionContext); cipherService.updateWithServer.mockRejectedValue(new Error("Internal error")); const result = async () => await authenticator.makeCredential(params, windowReference); @@ -357,13 +358,13 @@ describe("FidoAuthenticatorService", () => { cipherService.decrypt.mockResolvedValue(cipher); cipherService.encrypt.mockImplementation(async (cipher) => { cipher.login.fido2Credentials[0].credentialId = credentialId; // Replace id for testability - return {} as any; + return { cipher: {} as any as Cipher, encryptedFor: userId }; }); - cipherService.createWithServer.mockImplementation(async (cipher) => { + cipherService.createWithServer.mockImplementation(async ({ cipher }) => { cipher.id = cipherId; return cipher; }); - cipherService.updateWithServer.mockImplementation(async (cipher) => { + cipherService.updateWithServer.mockImplementation(async ({ cipher }) => { cipher.id = cipherId; return cipher; }); diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 6be89a4b376..d9f7ba19a6f 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -180,9 +180,7 @@ export class DefaultSdkService implements SdkService { return () => client?.markForDisposal(); }); }), - tap({ - finalize: () => this.sdkClientCache.delete(userId), - }), + tap({ finalize: () => this.sdkClientCache.delete(userId) }), shareReplay({ refCount: true, bufferSize: 1 }), ); @@ -205,9 +203,7 @@ export class DefaultSdkService implements SdkService { method: { decryptedKey: { decrypted_user_key: userKey.keyB64 } }, kdfParams: kdfParams.kdfType === KdfType.PBKDF2_SHA256 - ? { - pBKDF2: { iterations: kdfParams.iterations }, - } + ? { pBKDF2: { iterations: kdfParams.iterations } } : { argon2id: { iterations: kdfParams.iterations, diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index fc809058161..91f8006d15e 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -21,6 +21,12 @@ import { CipherView } from "../models/view/cipher.view"; import { FieldView } from "../models/view/field.view"; import { AddEditCipherInfo } from "../types/add-edit-cipher-info"; +export type EncryptionContext = { + cipher: Cipher; + /** The Id of the user that encrypted the cipher. It should always represent a UserId, even for Organization-owned ciphers */ + encryptedFor: UserId; +}; + export abstract class CipherService implements UserKeyRotationDataProvider<CipherWithIdRequest> { abstract cipherViews$(userId: UserId): Observable<CipherView[]>; abstract ciphers$(userId: UserId): Observable<Record<CipherId, CipherData>>; @@ -42,7 +48,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe keyForEncryption?: SymmetricCryptoKey, keyForCipherKeyDecryption?: SymmetricCryptoKey, originalCipher?: Cipher, - ): Promise<Cipher>; + ): Promise<EncryptionContext>; abstract encryptFields(fieldsModel: FieldView[], key: SymmetricCryptoKey): Promise<Field[]>; abstract encryptField(fieldModel: FieldView, key: SymmetricCryptoKey): Promise<Field>; abstract get(id: string, userId: UserId): Promise<Cipher>; @@ -94,7 +100,10 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe * * @returns A promise that resolves to the created cipher */ - abstract createWithServer(cipher: Cipher, orgAdmin?: boolean): Promise<Cipher>; + abstract createWithServer( + { cipher, encryptedFor }: EncryptionContext, + orgAdmin?: boolean, + ): Promise<Cipher>; /** * Update a cipher with the server * @param cipher The cipher to update @@ -104,7 +113,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe * @returns A promise that resolves to the updated cipher */ abstract updateWithServer( - cipher: Cipher, + { cipher, encryptedFor }: EncryptionContext, orgAdmin?: boolean, isNotClone?: boolean, ): Promise<Cipher>; diff --git a/libs/common/src/vault/models/data/field.data.ts b/libs/common/src/vault/models/data/field.data.ts index b9daf7fa423..cf9df69a6b0 100644 --- a/libs/common/src/vault/models/data/field.data.ts +++ b/libs/common/src/vault/models/data/field.data.ts @@ -7,7 +7,7 @@ export class FieldData { type: FieldType; name: string; value: string; - linkedId: LinkedIdType; + linkedId: LinkedIdType | null; constructor(response?: FieldApi) { if (response == null) { diff --git a/libs/common/src/vault/models/request/cipher-bulk-share.request.ts b/libs/common/src/vault/models/request/cipher-bulk-share.request.ts index 4f56297d0a5..d0c394bea00 100644 --- a/libs/common/src/vault/models/request/cipher-bulk-share.request.ts +++ b/libs/common/src/vault/models/request/cipher-bulk-share.request.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { UserId } from "../../../types/guid"; import { Cipher } from "../domain/cipher"; import { CipherWithIdRequest } from "./cipher-with-id.request"; @@ -8,11 +9,15 @@ export class CipherBulkShareRequest { ciphers: CipherWithIdRequest[]; collectionIds: string[]; - constructor(ciphers: Cipher[], collectionIds: string[]) { + constructor( + ciphers: Cipher[], + collectionIds: string[], + readonly encryptedFor: UserId, + ) { if (ciphers != null) { this.ciphers = []; ciphers.forEach((c) => { - this.ciphers.push(new CipherWithIdRequest(c)); + this.ciphers.push(new CipherWithIdRequest({ cipher: c, encryptedFor })); }); } this.collectionIds = collectionIds; diff --git a/libs/common/src/vault/models/request/cipher-create.request.ts b/libs/common/src/vault/models/request/cipher-create.request.ts index 9c3be5544b9..e992ebed9b2 100644 --- a/libs/common/src/vault/models/request/cipher-create.request.ts +++ b/libs/common/src/vault/models/request/cipher-create.request.ts @@ -1,4 +1,4 @@ -import { Cipher } from "../domain/cipher"; +import { EncryptionContext } from "../../abstractions/cipher.service"; import { CipherRequest } from "./cipher.request"; @@ -6,8 +6,8 @@ export class CipherCreateRequest { cipher: CipherRequest; collectionIds: string[]; - constructor(cipher: Cipher) { - this.cipher = new CipherRequest(cipher); + constructor({ cipher, encryptedFor }: EncryptionContext) { + this.cipher = new CipherRequest({ cipher, encryptedFor }); this.collectionIds = cipher.collectionIds; } } diff --git a/libs/common/src/vault/models/request/cipher-share.request.ts b/libs/common/src/vault/models/request/cipher-share.request.ts index 4043599ce05..17c46168efe 100644 --- a/libs/common/src/vault/models/request/cipher-share.request.ts +++ b/libs/common/src/vault/models/request/cipher-share.request.ts @@ -1,4 +1,4 @@ -import { Cipher } from "../domain/cipher"; +import { EncryptionContext } from "../../abstractions/cipher.service"; import { CipherRequest } from "./cipher.request"; @@ -6,8 +6,8 @@ export class CipherShareRequest { cipher: CipherRequest; collectionIds: string[]; - constructor(cipher: Cipher) { - this.cipher = new CipherRequest(cipher); + constructor({ cipher, encryptedFor }: EncryptionContext) { + this.cipher = new CipherRequest({ cipher, encryptedFor }); this.collectionIds = cipher.collectionIds; } } diff --git a/libs/common/src/vault/models/request/cipher-with-id.request.ts b/libs/common/src/vault/models/request/cipher-with-id.request.ts index f291e342640..0b04f50fb1e 100644 --- a/libs/common/src/vault/models/request/cipher-with-id.request.ts +++ b/libs/common/src/vault/models/request/cipher-with-id.request.ts @@ -1,12 +1,12 @@ -import { Cipher } from "../domain/cipher"; +import { EncryptionContext } from "../../abstractions/cipher.service"; import { CipherRequest } from "./cipher.request"; export class CipherWithIdRequest extends CipherRequest { id: string; - constructor(cipher: Cipher) { - super(cipher); + constructor({ cipher, encryptedFor }: EncryptionContext) { + super({ cipher, encryptedFor }); this.id = cipher.id; } } diff --git a/libs/common/src/vault/models/request/cipher.request.ts b/libs/common/src/vault/models/request/cipher.request.ts index 5b77ee7508e..2e3b2efbedc 100644 --- a/libs/common/src/vault/models/request/cipher.request.ts +++ b/libs/common/src/vault/models/request/cipher.request.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { UserId } from "../../../types/guid"; +import { EncryptionContext } from "../../abstractions/cipher.service"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; import { CipherType } from "../../enums/cipher-type"; import { CardApi } from "../api/card.api"; @@ -10,12 +12,12 @@ import { LoginUriApi } from "../api/login-uri.api"; import { LoginApi } from "../api/login.api"; import { SecureNoteApi } from "../api/secure-note.api"; import { SshKeyApi } from "../api/ssh-key.api"; -import { Cipher } from "../domain/cipher"; import { AttachmentRequest } from "./attachment.request"; import { PasswordHistoryRequest } from "./password-history.request"; export class CipherRequest { + encryptedFor: UserId; type: CipherType; folderId: string; organizationId: string; @@ -36,8 +38,9 @@ export class CipherRequest { reprompt: CipherRepromptType; key: string; - constructor(cipher: Cipher) { + constructor({ cipher, encryptedFor }: EncryptionContext) { this.type = cipher.type; + this.encryptedFor = encryptedFor; this.folderId = cipher.folderId; this.organizationId = cipher.organizationId; this.name = cipher.name ? cipher.name.encryptedString : null; diff --git a/libs/common/src/vault/models/view/cipher.view.ts b/libs/common/src/vault/models/view/cipher.view.ts index 1f73903a5bc..e182025a332 100644 --- a/libs/common/src/vault/models/view/cipher.view.ts +++ b/libs/common/src/vault/models/view/cipher.view.ts @@ -25,7 +25,7 @@ export class CipherView implements View, InitializerMetadata { readonly initializerKey = InitializerKey.CipherView; id: string = null; - organizationId: string = null; + organizationId: string | undefined = null; folderId: string = null; name: string = null; notes: string = null; diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index 9e56bac2ca0..1a0b1568775 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -27,6 +27,7 @@ import { ContainerService } from "../../platform/services/container.service"; import { CipherId, UserId } from "../../types/guid"; import { CipherKey, OrgKey, UserKey } from "../../types/key"; import { CipherEncryptionService } from "../abstractions/cipher-encryption.service"; +import { EncryptionContext } from "../abstractions/cipher.service"; import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file-upload.service"; import { FieldType } from "../enums"; import { CipherRepromptType } from "../enums/cipher-reprompt-type"; @@ -78,36 +79,12 @@ const cipherData: CipherData = { }, passwordHistory: [{ password: "EncryptedString", lastUsedDate: "2022-01-31T12:00:00.000Z" }], attachments: [ - { - id: "a1", - url: "url", - size: "1100", - sizeName: "1.1 KB", - fileName: "file", - key: "EncKey", - }, - { - id: "a2", - url: "url", - size: "1100", - sizeName: "1.1 KB", - fileName: "file", - key: "EncKey", - }, + { id: "a1", url: "url", size: "1100", sizeName: "1.1 KB", fileName: "file", key: "EncKey" }, + { id: "a2", url: "url", size: "1100", sizeName: "1.1 KB", fileName: "file", key: "EncKey" }, ], fields: [ - { - name: "EncryptedString", - value: "EncryptedString", - type: FieldType.Text, - linkedId: null, - }, - { - name: "EncryptedString", - value: "EncryptedString", - type: FieldType.Hidden, - linkedId: null, - }, + { name: "EncryptedString", value: "EncryptedString", type: FieldType.Text, linkedId: null }, + { name: "EncryptedString", value: "EncryptedString", type: FieldType.Hidden, linkedId: null }, ], }; const mockUserId = Utils.newGuid() as UserId; @@ -133,7 +110,7 @@ describe("Cipher Service", () => { const userId = "TestUserId" as UserId; let cipherService: CipherService; - let cipherObj: Cipher; + let encryptionContext: EncryptionContext; beforeEach(() => { encryptService.encryptFileData.mockReturnValue(Promise.resolve(ENCRYPTED_BYTES)); @@ -159,7 +136,7 @@ describe("Cipher Service", () => { cipherEncryptionService, ); - cipherObj = new Cipher(cipherData); + encryptionContext = { cipher: new Cipher(cipherData), encryptedFor: userId }; }); afterEach(() => { @@ -192,33 +169,33 @@ describe("Cipher Service", () => { it("should call apiService.postCipherAdmin when orgAdmin param is true and the cipher orgId != null", async () => { const spy = jest .spyOn(apiService, "postCipherAdmin") - .mockImplementation(() => Promise.resolve<any>(cipherObj.toCipherData())); - await cipherService.createWithServer(cipherObj, true); - const expectedObj = new CipherCreateRequest(cipherObj); + .mockImplementation(() => Promise.resolve<any>(encryptionContext.cipher.toCipherData())); + await cipherService.createWithServer(encryptionContext, true); + const expectedObj = new CipherCreateRequest(encryptionContext); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith(expectedObj); }); it("should call apiService.postCipher when orgAdmin param is true and the cipher orgId is null", async () => { - cipherObj.organizationId = null; + encryptionContext.cipher.organizationId = null!; const spy = jest .spyOn(apiService, "postCipher") - .mockImplementation(() => Promise.resolve<any>(cipherObj.toCipherData())); - await cipherService.createWithServer(cipherObj, true); - const expectedObj = new CipherRequest(cipherObj); + .mockImplementation(() => Promise.resolve<any>(encryptionContext.cipher.toCipherData())); + await cipherService.createWithServer(encryptionContext, true); + const expectedObj = new CipherRequest(encryptionContext); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith(expectedObj); }); it("should call apiService.postCipherCreate if collectionsIds != null", async () => { - cipherObj.collectionIds = ["123"]; + encryptionContext.cipher.collectionIds = ["123"]; const spy = jest .spyOn(apiService, "postCipherCreate") - .mockImplementation(() => Promise.resolve<any>(cipherObj.toCipherData())); - await cipherService.createWithServer(cipherObj); - const expectedObj = new CipherCreateRequest(cipherObj); + .mockImplementation(() => Promise.resolve<any>(encryptionContext.cipher.toCipherData())); + await cipherService.createWithServer(encryptionContext); + const expectedObj = new CipherCreateRequest(encryptionContext); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith(expectedObj); @@ -227,9 +204,9 @@ describe("Cipher Service", () => { it("should call apiService.postCipher when orgAdmin and collectionIds logic is false", async () => { const spy = jest .spyOn(apiService, "postCipher") - .mockImplementation(() => Promise.resolve<any>(cipherObj.toCipherData())); - await cipherService.createWithServer(cipherObj); - const expectedObj = new CipherRequest(cipherObj); + .mockImplementation(() => Promise.resolve<any>(encryptionContext.cipher.toCipherData())); + await cipherService.createWithServer(encryptionContext); + const expectedObj = new CipherRequest(encryptionContext); expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledWith(expectedObj); @@ -240,36 +217,36 @@ describe("Cipher Service", () => { it("should call apiService.putCipherAdmin when orgAdmin param is true", async () => { const spy = jest .spyOn(apiService, "putCipherAdmin") - .mockImplementation(() => Promise.resolve<any>(cipherObj.toCipherData())); - await cipherService.updateWithServer(cipherObj, true); - const expectedObj = new CipherRequest(cipherObj); + .mockImplementation(() => Promise.resolve<any>(encryptionContext.cipher.toCipherData())); + await cipherService.updateWithServer(encryptionContext, true); + const expectedObj = new CipherRequest(encryptionContext); expect(spy).toHaveBeenCalled(); - expect(spy).toHaveBeenCalledWith(cipherObj.id, expectedObj); + expect(spy).toHaveBeenCalledWith(encryptionContext.cipher.id, expectedObj); }); it("should call apiService.putCipher if cipher.edit is true", async () => { - cipherObj.edit = true; + encryptionContext.cipher.edit = true; const spy = jest .spyOn(apiService, "putCipher") - .mockImplementation(() => Promise.resolve<any>(cipherObj.toCipherData())); - await cipherService.updateWithServer(cipherObj); - const expectedObj = new CipherRequest(cipherObj); + .mockImplementation(() => Promise.resolve<any>(encryptionContext.cipher.toCipherData())); + await cipherService.updateWithServer(encryptionContext); + const expectedObj = new CipherRequest(encryptionContext); expect(spy).toHaveBeenCalled(); - expect(spy).toHaveBeenCalledWith(cipherObj.id, expectedObj); + expect(spy).toHaveBeenCalledWith(encryptionContext.cipher.id, expectedObj); }); it("should call apiService.putPartialCipher when orgAdmin, and edit are false", async () => { - cipherObj.edit = false; + encryptionContext.cipher.edit = false; const spy = jest .spyOn(apiService, "putPartialCipher") - .mockImplementation(() => Promise.resolve<any>(cipherObj.toCipherData())); - await cipherService.updateWithServer(cipherObj); - const expectedObj = new CipherPartialRequest(cipherObj); + .mockImplementation(() => Promise.resolve<any>(encryptionContext.cipher.toCipherData())); + await cipherService.updateWithServer(encryptionContext); + const expectedObj = new CipherPartialRequest(encryptionContext.cipher); expect(spy).toHaveBeenCalled(); - expect(spy).toHaveBeenCalledWith(cipherObj.id, expectedObj); + expect(spy).toHaveBeenCalledWith(encryptionContext.cipher.id, expectedObj); }); }); @@ -293,6 +270,15 @@ describe("Cipher Service", () => { jest.spyOn(cipherService as any, "getAutofillOnPageLoadDefault").mockResolvedValue(true); }); + it("should return the encrypting user id", async () => { + keyService.getOrgKey.mockReturnValue( + Promise.resolve<any>(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey), + ); + + const { encryptedFor } = await cipherService.encrypt(cipherView, userId); + expect(encryptedFor).toEqual(userId); + }); + describe("login encryption", () => { it("should add a uri hash to login uris", async () => { encryptService.hash.mockImplementation((value) => Promise.resolve(`${value} hash`)); @@ -304,9 +290,9 @@ describe("Cipher Service", () => { Promise.resolve<any>(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey), ); - const domain = await cipherService.encrypt(cipherView, userId); + const { cipher } = await cipherService.encrypt(cipherView, userId); - expect(domain.login.uris).toEqual([ + expect(cipher.login.uris).toEqual([ { uri: new EncString("uri has been encrypted"), uriChecksum: new EncString("uri hash has been encrypted"), @@ -325,7 +311,7 @@ describe("Cipher Service", () => { it("is null when feature flag is false", async () => { configService.getFeatureFlag.mockResolvedValue(false); - const cipher = await cipherService.encrypt(cipherView, userId); + const { cipher } = await cipherService.encrypt(cipherView, userId); expect(cipher.key).toBeNull(); }); @@ -338,7 +324,7 @@ describe("Cipher Service", () => { it("is null when the cipher is not viewPassword", async () => { cipherView.viewPassword = false; - const cipher = await cipherService.encrypt(cipherView, userId); + const { cipher } = await cipherService.encrypt(cipherView, userId); expect(cipher.key).toBeNull(); }); @@ -346,7 +332,7 @@ describe("Cipher Service", () => { it("is defined when the cipher is viewPassword", async () => { cipherView.viewPassword = true; - const cipher = await cipherService.encrypt(cipherView, userId); + const { cipher } = await cipherService.encrypt(cipherView, userId); expect(cipher.key).toBeDefined(); }); @@ -393,7 +379,13 @@ describe("Cipher Service", () => { it("is called when cipher viewPassword is false and original cipher has a key", async () => { cipherView.viewPassword = false; - await cipherService.encrypt(cipherView, userId, undefined, undefined, cipherObj); + await cipherService.encrypt( + cipherView, + userId, + undefined, + undefined, + encryptionContext.cipher, + ); expect(cipherService["encryptCipherWithCipherKey"]).toHaveBeenCalled(); }); @@ -416,22 +408,17 @@ describe("Cipher Service", () => { stateService.getUserId.mockResolvedValue(mockUserId); - const keys = { - userKey: originalUserKey, - } as CipherDecryptionKeys; + const keys = { userKey: originalUserKey } as CipherDecryptionKeys; keyService.cipherDecryptionKeys$.mockReturnValue(of(keys)); - const cipher1 = new CipherView(cipherObj); - cipher1.id = "Cipher 1"; + const cipher1 = new CipherView(encryptionContext.cipher); + cipher1.id = "Cipher 1" as CipherId; cipher1.organizationId = null; - const cipher2 = new CipherView(cipherObj); - cipher2.id = "Cipher 2"; + const cipher2 = new CipherView(encryptionContext.cipher); + cipher2.id = "Cipher 2" as CipherId; cipher2.organizationId = null; - decryptedCiphers = new BehaviorSubject({ - Cipher1: cipher1, - Cipher2: cipher2, - }); + decryptedCiphers = new BehaviorSubject({ [cipher1.id]: cipher1, [cipher2.id]: cipher2 }); jest .spyOn(cipherService, "cipherViews$") .mockImplementation((userId: UserId) => @@ -462,19 +449,19 @@ describe("Cipher Service", () => { }); it("throws if the original user key is null", async () => { - await expect(cipherService.getRotatedData(null, newUserKey, mockUserId)).rejects.toThrow( + await expect(cipherService.getRotatedData(null!, newUserKey, mockUserId)).rejects.toThrow( "Original user key is required to rotate ciphers", ); }); it("throws if the new user key is null", async () => { - await expect(cipherService.getRotatedData(originalUserKey, null, mockUserId)).rejects.toThrow( - "New user key is required to rotate ciphers", - ); + await expect( + cipherService.getRotatedData(originalUserKey, null!, mockUserId), + ).rejects.toThrow("New user key is required to rotate ciphers"); }); it("throws if the user has any failed to decrypt ciphers", async () => { - const badCipher = new CipherView(cipherObj); + const badCipher = new CipherView(encryptionContext.cipher); badCipher.id = "Cipher 3"; badCipher.organizationId = null; badCipher.decryptionFailure = true; @@ -488,12 +475,15 @@ describe("Cipher Service", () => { describe("decrypt", () => { it("should call decrypt method of CipherEncryptionService when feature flag is true", async () => { configService.getFeatureFlag.mockResolvedValue(true); - cipherEncryptionService.decrypt.mockResolvedValue(new CipherView(cipherObj)); + cipherEncryptionService.decrypt.mockResolvedValue(new CipherView(encryptionContext.cipher)); - const result = await cipherService.decrypt(cipherObj, userId); + const result = await cipherService.decrypt(encryptionContext.cipher, userId); - expect(result).toEqual(new CipherView(cipherObj)); - expect(cipherEncryptionService.decrypt).toHaveBeenCalledWith(cipherObj, userId); + expect(result).toEqual(new CipherView(encryptionContext.cipher)); + expect(cipherEncryptionService.decrypt).toHaveBeenCalledWith( + encryptionContext.cipher, + userId, + ); }); it("should call legacy decrypt when feature flag is false", async () => { @@ -501,12 +491,14 @@ describe("Cipher Service", () => { configService.getFeatureFlag.mockResolvedValue(false); cipherService.getKeyForCipherKeyDecryption = jest.fn().mockResolvedValue(mockUserKey); encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32)); - jest.spyOn(cipherObj, "decrypt").mockResolvedValue(new CipherView(cipherObj)); + jest + .spyOn(encryptionContext.cipher, "decrypt") + .mockResolvedValue(new CipherView(encryptionContext.cipher)); - const result = await cipherService.decrypt(cipherObj, userId); + const result = await cipherService.decrypt(encryptionContext.cipher, userId); - expect(result).toEqual(new CipherView(cipherObj)); - expect(cipherObj.decrypt).toHaveBeenCalledWith(mockUserKey); + expect(result).toEqual(new CipherView(encryptionContext.cipher)); + expect(encryptionContext.cipher.decrypt).toHaveBeenCalledWith(mockUserKey); }); }); diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 2693d9d4644..0c948fe0c6b 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -33,7 +33,10 @@ import { CipherId, CollectionId, OrganizationId, UserId } from "../../types/guid import { OrgKey, UserKey } from "../../types/key"; import { filterOutNullish, perUserCache$ } from "../../vault/utils/observable-utilities"; import { CipherEncryptionService } from "../abstractions/cipher-encryption.service"; -import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service"; +import { + CipherService as CipherServiceAbstraction, + EncryptionContext, +} from "../abstractions/cipher.service"; import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file-upload.service"; import { FieldType } from "../enums"; import { CipherType } from "../enums/cipher-type"; @@ -196,7 +199,7 @@ export class CipherService implements CipherServiceAbstraction { keyForCipherEncryption?: SymmetricCryptoKey, keyForCipherKeyDecryption?: SymmetricCryptoKey, originalCipher: Cipher = null, - ): Promise<Cipher> { + ): Promise<EncryptionContext> { if (model.id != null) { if (originalCipher == null) { originalCipher = await this.get(model.id, userId); @@ -230,18 +233,24 @@ export class CipherService implements CipherServiceAbstraction { keyForCipherEncryption ||= userOrOrgKey; // If the caller has provided a key for cipher key decryption, use it. Otherwise, use the user or org key. keyForCipherKeyDecryption ||= userOrOrgKey; - return this.encryptCipherWithCipherKey( - model, - cipher, - keyForCipherEncryption, - keyForCipherKeyDecryption, - ); + return { + cipher: await this.encryptCipherWithCipherKey( + model, + cipher, + keyForCipherEncryption, + keyForCipherKeyDecryption, + ), + encryptedFor: userId, + }; } else { keyForCipherEncryption ||= await this.getKeyForCipherKeyDecryption(cipher, userId); // We want to ensure that the cipher key is null if cipher key encryption is disabled // so that decryption uses the proper key. cipher.key = null; - return this.encryptCipher(model, cipher, keyForCipherEncryption); + return { + cipher: await this.encryptCipher(model, cipher, keyForCipherEncryption), + encryptedFor: userId, + }; } } @@ -261,19 +270,14 @@ export class CipherService implements CipherServiceAbstraction { attachment.size = model.size; attachment.sizeName = model.sizeName; attachment.url = model.url; - const promise = this.encryptObjProperty( - model, - attachment, - { - fileName: null, + const promise = this.encryptObjProperty(model, attachment, { fileName: null }, key).then( + async () => { + if (model.key != null) { + attachment.key = await this.encryptService.wrapSymmetricKey(model.key, key); + } + encAttachments.push(attachment); }, - key, - ).then(async () => { - if (model.key != null) { - attachment.key = await this.encryptService.wrapSymmetricKey(model.key, key); - } - encAttachments.push(attachment); - }); + ); promises.push(promise); }); @@ -306,15 +310,7 @@ export class CipherService implements CipherServiceAbstraction { fieldModel.value = "false"; } - await this.encryptObjProperty( - fieldModel, - field, - { - name: null, - value: null, - }, - key, - ); + await this.encryptObjProperty(fieldModel, field, { name: null, value: null }, key); return field; } @@ -345,14 +341,7 @@ export class CipherService implements CipherServiceAbstraction { const ph = new Password(); ph.lastUsedDate = phModel.lastUsedDate; - await this.encryptObjProperty( - phModel, - ph, - { - password: null, - }, - key, - ); + await this.encryptObjProperty(phModel, ph, { password: null }, key); return ph; } @@ -705,9 +694,7 @@ export class CipherService implements CipherServiceAbstraction { if (ciphersLocalData[cipherId]) { ciphersLocalData[cipherId].lastUsedDate = new Date().getTime(); } else { - ciphersLocalData[cipherId] = { - lastUsedDate: new Date().getTime(), - }; + ciphersLocalData[cipherId] = { lastUsedDate: new Date().getTime() }; } await this.localDataState(userId).update(() => ciphersLocalData); @@ -735,10 +722,7 @@ export class CipherService implements CipherServiceAbstraction { } const currentTime = new Date().getTime(); - ciphersLocalData[id as CipherId] = { - lastLaunched: currentTime, - lastUsedDate: currentTime, - }; + ciphersLocalData[id as CipherId] = { lastLaunched: currentTime, lastUsedDate: currentTime }; await this.localDataState(userId).update(() => ciphersLocalData); @@ -770,18 +754,21 @@ export class CipherService implements CipherServiceAbstraction { await this.domainSettingsService.setNeverDomains(domains); } - async createWithServer(cipher: Cipher, orgAdmin?: boolean): Promise<Cipher> { + async createWithServer( + { cipher, encryptedFor }: EncryptionContext, + orgAdmin?: boolean, + ): Promise<Cipher> { let response: CipherResponse; if (orgAdmin && cipher.organizationId != null) { - const request = new CipherCreateRequest(cipher); + const request = new CipherCreateRequest({ cipher, encryptedFor }); response = await this.apiService.postCipherAdmin(request); const data = new CipherData(response, cipher.collectionIds); return new Cipher(data); } else if (cipher.collectionIds != null) { - const request = new CipherCreateRequest(cipher); + const request = new CipherCreateRequest({ cipher, encryptedFor }); response = await this.apiService.postCipherCreate(request); } else { - const request = new CipherRequest(cipher); + const request = new CipherRequest({ cipher, encryptedFor }); response = await this.apiService.postCipher(request); } cipher.id = response.id; @@ -792,15 +779,18 @@ export class CipherService implements CipherServiceAbstraction { return new Cipher(updated[cipher.id as CipherId]); } - async updateWithServer(cipher: Cipher, orgAdmin?: boolean): Promise<Cipher> { + async updateWithServer( + { cipher, encryptedFor }: EncryptionContext, + orgAdmin?: boolean, + ): Promise<Cipher> { let response: CipherResponse; if (orgAdmin) { - const request = new CipherRequest(cipher); + const request = new CipherRequest({ cipher, encryptedFor }); response = await this.apiService.putCipherAdmin(cipher.id, request); const data = new CipherData(response, cipher.collectionIds); return new Cipher(data, cipher.localData); } else if (cipher.edit) { - const request = new CipherRequest(cipher); + const request = new CipherRequest({ cipher, encryptedFor }); response = await this.apiService.putCipher(cipher.id, request); } else { const request = new CipherPartialRequest(cipher); @@ -854,12 +844,12 @@ export class CipherService implements CipherServiceAbstraction { cipher.collectionIds = collectionIds; promises.push( this.encryptSharedCipher(cipher, userId).then((c) => { - encCiphers.push(c); + encCiphers.push(c.cipher); }), ); } await Promise.all(promises); - const request = new CipherBulkShareRequest(encCiphers, collectionIds); + const request = new CipherBulkShareRequest(encCiphers, collectionIds, userId); try { await this.apiService.putShareCiphers(request); } catch (e) { @@ -921,8 +911,8 @@ export class CipherService implements CipherServiceAbstraction { //in order to keep item and it's attachments with the same encryption level if (cipher.key != null && !cipherKeyEncryptionEnabled) { const model = await this.decrypt(cipher, userId); - cipher = await this.encrypt(model, userId); - await this.updateWithServer(cipher); + const reEncrypted = await this.encrypt(model, userId); + await this.updateWithServer(reEncrypted); } const encFileName = await this.encryptService.encryptString(filename, cipherEncKey); @@ -1482,7 +1472,7 @@ export class CipherService implements CipherServiceAbstraction { // In the case of a cipher that is being shared with an organization, we want to decrypt the // cipher key with the user's key and then re-encrypt it with the organization's key. - private async encryptSharedCipher(model: CipherView, userId: UserId): Promise<Cipher> { + private async encryptSharedCipher(model: CipherView, userId: UserId): Promise<EncryptionContext> { const keyForCipherKeyDecryption = await this.keyService.getUserKeyWithLegacySupport(userId); return await this.encrypt(model, userId, null, keyForCipherKeyDecryption); } @@ -1584,10 +1574,7 @@ export class CipherService implements CipherServiceAbstraction { fd.append( "data", Buffer.from(encData.buffer) as any, - { - filepath: encFileName.encryptedString, - contentType: "application/octet-stream", - } as any, + { filepath: encFileName.encryptedString, contentType: "application/octet-stream" } as any, ); } else { throw e; @@ -1649,11 +1636,7 @@ export class CipherService implements CipherServiceAbstraction { await this.encryptObjProperty( model.login, cipher.login, - { - username: null, - password: null, - totp: null, - }, + { username: null, password: null, totp: null }, key, ); @@ -1663,14 +1646,7 @@ export class CipherService implements CipherServiceAbstraction { for (let i = 0; i < model.login.uris.length; i++) { const loginUri = new LoginUri(); loginUri.match = model.login.uris[i].match; - await this.encryptObjProperty( - model.login.uris[i], - loginUri, - { - uri: null, - }, - key, - ); + await this.encryptObjProperty(model.login.uris[i], loginUri, { uri: null }, key); const uriHash = await this.encryptService.hash(model.login.uris[i].uri, "sha256"); loginUri.uriChecksum = await this.encryptService.encryptString(uriHash, key); cipher.login.uris.push(loginUri); @@ -1766,11 +1742,7 @@ export class CipherService implements CipherServiceAbstraction { await this.encryptObjProperty( model.sshKey, cipher.sshKey, - { - privateKey: null, - publicKey: null, - keyFingerprint: null, - }, + { privateKey: null, publicKey: null, keyFingerprint: null }, key, ); return; @@ -1855,15 +1827,7 @@ export class CipherService implements CipherServiceAbstraction { } await Promise.all([ - this.encryptObjProperty( - model, - cipher, - { - name: null, - notes: null, - }, - key, - ), + this.encryptObjProperty(model, cipher, { name: null, notes: null }, key), this.encryptCipherData(cipher, model, key), this.encryptFields(model.fields, key).then((fields) => { cipher.fields = fields; diff --git a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts index 68eac4f0da2..99f853d4c86 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts @@ -29,19 +29,20 @@ export class DefaultCipherFormService implements CipherFormService { async saveCipher(cipher: CipherView, config: CipherFormConfig): Promise<CipherView> { // Passing the original cipher is important here as it is responsible for appending to password history const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - const encryptedCipher = await this.cipherService.encrypt( + const encrypted = await this.cipherService.encrypt( cipher, activeUserId, null, null, config.originalCipher ?? null, ); + const encryptedCipher = encrypted.cipher; let savedCipher: Cipher; // Creating a new cipher if (cipher.id == null) { - savedCipher = await this.cipherService.createWithServer(encryptedCipher, config.admin); + savedCipher = await this.cipherService.createWithServer(encrypted, config.admin); return await this.cipherService.decrypt(savedCipher, activeUserId); } @@ -64,13 +65,13 @@ export class DefaultCipherFormService implements CipherFormService { ); // If the collectionIds are the same, update the cipher normally } else if (isSetEqual(originalCollectionIds, newCollectionIds)) { - savedCipher = await this.cipherService.updateWithServer(encryptedCipher, config.admin); + savedCipher = await this.cipherService.updateWithServer(encrypted, config.admin); } else { // Updating a cipher with collection changes is not supported with a single request currently // First update the cipher with the original collectionIds encryptedCipher.collectionIds = config.originalCipher.collectionIds; await this.cipherService.updateWithServer( - encryptedCipher, + encrypted, config.admin || originalCollectionIds.size === 0, ); diff --git a/libs/vault/src/components/assign-collections.component.ts b/libs/vault/src/components/assign-collections.component.ts index faa2dae072a..4a0bd1fc670 100644 --- a/libs/vault/src/components/assign-collections.component.ts +++ b/libs/vault/src/components/assign-collections.component.ts @@ -506,7 +506,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI private async updateAssignedCollections(cipherView: CipherView, userId: UserId) { const { collections } = this.formGroup.getRawValue(); cipherView.collectionIds = collections.map((i) => i.id as CollectionId); - const cipher = await this.cipherService.encrypt(cipherView, userId); + const { cipher } = await this.cipherService.encrypt(cipherView, userId); if (this.params.isSingleCipherAdmin) { await this.cipherService.saveCollectionsWithServerAdmin(cipher); } else { From eba22cf5f8b7f9bdcbc9e37f820165d6f04819b1 Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Fri, 30 May 2025 13:45:31 -0500 Subject: [PATCH 201/245] [PM-21797] Require userID for keyService's getUserKeyFromStorage (#14855) * require userID for keyService's getUserKeyFromStorage --- .../src/abstractions/key.service.ts | 3 +- libs/key-management/src/key.service.spec.ts | 81 +++++++++++++++++++ libs/key-management/src/key.service.ts | 5 +- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/libs/key-management/src/abstractions/key.service.ts b/libs/key-management/src/abstractions/key.service.ts index 51a99421967..7a9c076a8bb 100644 --- a/libs/key-management/src/abstractions/key.service.ts +++ b/libs/key-management/src/abstractions/key.service.ts @@ -128,10 +128,11 @@ export abstract class KeyService { * @param keySuffix The desired version of the user's key to retrieve * @param userId The desired user * @returns The user key + * @throws Error when userId is null or undefined. */ abstract getUserKeyFromStorage( keySuffix: KeySuffixOptions, - userId?: string, + userId: string, ): Promise<UserKey | null>; /** diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index 400d7279a30..cd5458e9a1f 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -14,6 +14,7 @@ import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/ke import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; import { Encrypted } from "@bitwarden/common/platform/interfaces/encrypted"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString, EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -896,4 +897,84 @@ describe("keyService", () => { }); }); }); + + describe("getUserKeyFromStorage", () => { + let mockUserKey: UserKey; + let validateUserKeySpy: jest.SpyInstance; + + beforeEach(() => { + mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey; + validateUserKeySpy = jest.spyOn(keyService, "validateUserKey"); + }); + + afterEach(() => { + validateUserKeySpy.mockRestore(); + }); + + describe("input validation", () => { + const invalidUserIdTestCases = [ + { keySuffix: KeySuffixOptions.Auto, userId: null as unknown as UserId }, + { keySuffix: KeySuffixOptions.Auto, userId: undefined as unknown as UserId }, + { keySuffix: KeySuffixOptions.Pin, userId: null as unknown as UserId }, + { keySuffix: KeySuffixOptions.Pin, userId: undefined as unknown as UserId }, + ]; + + test.each(invalidUserIdTestCases)( + "throws when keySuffix is $keySuffix and userId is $userId", + async ({ keySuffix, userId }) => { + await expect(keyService.getUserKeyFromStorage(keySuffix, userId)).rejects.toThrow( + "UserId is required", + ); + }, + ); + }); + + describe("with Pin keySuffix", () => { + it("returns null and doesn't validate the key", async () => { + const result = await keyService.getUserKeyFromStorage(KeySuffixOptions.Pin, mockUserId); + + expect(result).toBeNull(); + expect(validateUserKeySpy).not.toHaveBeenCalled(); + }); + }); + + describe("with Auto keySuffix", () => { + it("returns validated key from storage when key exists and is valid", async () => { + stateService.getUserKeyAutoUnlock.mockResolvedValue(mockUserKey.keyB64); + validateUserKeySpy.mockResolvedValue(true); + + const result = await keyService.getUserKeyFromStorage(KeySuffixOptions.Auto, mockUserId); + + expect(result).toEqual(mockUserKey); + expect(validateUserKeySpy).toHaveBeenCalledWith(mockUserKey, mockUserId); + expect(stateService.getUserKeyAutoUnlock).toHaveBeenCalledWith({ + userId: mockUserId, + }); + }); + + it("returns null when no key is found in storage", async () => { + stateService.getUserKeyAutoUnlock.mockResolvedValue(null as unknown as string); + + const result = await keyService.getUserKeyFromStorage(KeySuffixOptions.Auto, mockUserId); + + expect(result).toBeNull(); + expect(validateUserKeySpy).not.toHaveBeenCalled(); + }); + + it("clears stored keys when userKey validation fails", async () => { + stateService.getUserKeyAutoUnlock.mockResolvedValue(mockUserKey.keyB64); + validateUserKeySpy.mockResolvedValue(false); + + const result = await keyService.getUserKeyFromStorage(KeySuffixOptions.Auto, mockUserId); + + expect(result).toEqual(mockUserKey); + expect(validateUserKeySpy).toHaveBeenCalledWith(mockUserKey, mockUserId); + expect(logService.warning).toHaveBeenCalledWith("Invalid key, throwing away stored keys"); + expect(pinService.clearPinKeyEncryptedUserKeyEphemeral).toHaveBeenCalledWith(mockUserId); + expect(stateService.setUserKeyAutoUnlock).toHaveBeenCalledWith(null, { + userId: mockUserId, + }); + }); + }); + }); }); diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index fe288adeb88..4a48d00f568 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -178,11 +178,10 @@ export class DefaultKeyService implements KeyServiceAbstraction { async getUserKeyFromStorage( keySuffix: KeySuffixOptions, - userId?: UserId, + userId: UserId, ): Promise<UserKey | null> { - userId ??= await firstValueFrom(this.stateProvider.activeUserId$); if (userId == null) { - throw new Error("No active user id found."); + throw new Error("UserId is required"); } const userKey = await this.getKeyFromStorage(keySuffix, userId); From 721657a5c30802edef34a20309c01ae2952b1da1 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Fri, 30 May 2025 15:31:44 -0400 Subject: [PATCH 202/245] Update syntax for Github. (#14845) --- .../angular/src/platform/view-cache/README.md | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/libs/angular/src/platform/view-cache/README.md b/libs/angular/src/platform/view-cache/README.md index c1f80da5800..d98222c4bde 100644 --- a/libs/angular/src/platform/view-cache/README.md +++ b/libs/angular/src/platform/view-cache/README.md @@ -43,12 +43,9 @@ on any component. The persistence layer ensures that the popup will open at the same route as was active when it closed, provided that none of the lifetime expiration events have occurred. -:::tip Excluding a route - -If a particular route should be excluded from the history and not persisted, add -`doNotSaveUrl: true` to the `data` property on the route. - -::: +> [!TIP] +> If a particular route should be excluded from the history and not persisted, add +> `doNotSaveUrl: true` to the `data` property on the route. ### View data persistence @@ -85,13 +82,10 @@ const mySignal = this.viewCacheService.signal({ mySignal.set("value") ``` -:::note Equality comparison - -By default, signals use `Object.is` to determine equality, and `set()` will only trigger updates if -the updated value is not equal to the current signal state. See documentation -[here](https://angular.dev/guide/signals#signal-equality-functions). - -::: +> [!NOTE] +> By default, signals use `Object.is` to determine equality, and `set()` will only trigger updates if +> the updated value is not equal to the current signal state. See documentation +> [here](https://angular.dev/guide/signals#signal-equality-functions). Putting this together, the most common implementation pattern would be: From f55f315ca15df09772e957e0e8b089a2d45b04f7 Mon Sep 17 00:00:00 2001 From: Kevinw778 <kevinw778@gmail.com> Date: Sat, 31 May 2025 05:18:28 -0400 Subject: [PATCH 203/245] [PM-21868] Send limit reached icon + message now show (#14860) * Send limit reached icon + message now show * Fix en/messages.json --------- Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> --- apps/browser/src/_locales/en/messages.json | 4 ++++ .../send-list-items-container.component.html | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index feb5a7706f3..29223942fd6 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2677,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html b/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html index 9b0f0fca26f..94ebfc3e5e6 100644 --- a/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html +++ b/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html @@ -26,6 +26,16 @@ ></i> </div> {{ send.name }} + <ng-container *ngIf="send.maxAccessCountReached"> + <i + class="bwi bwi-exclamation-triangle" + appStopProp + title="{{ 'maxAccessCountReached' | i18n }}" + aria-hidden="true" + ></i> + <span class="tw-sr-only">{{ "maxAccessCountReached" | i18n }}</span> + </ng-container> + <span slot="secondary"> {{ "deletionDate" | i18n }}: {{ send.deletionDate | date: "mediumDate" }} </span> From 960f6938f46287e2d336dbe2a125acb075411d2c Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 08:59:32 +0000 Subject: [PATCH 204/245] Autosync the updated translations (#15024) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/zh_CN/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 4a001fc3b05..9baa14bc03d 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -247,7 +247,7 @@ "message": "记住 SSH 授权" }, "sshAgentPromptBehaviorAlways": { - "message": "总是" + "message": "始终" }, "sshAgentPromptBehaviorNever": { "message": "从不" From 412546506ac5ed51c7cfccba9648d5cf278be0e1 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 08:59:46 +0000 Subject: [PATCH 205/245] Autosync the updated translations (#15025) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 13 +++++++++++++ apps/web/src/locales/ar/messages.json | 13 +++++++++++++ apps/web/src/locales/az/messages.json | 17 +++++++++++++++-- apps/web/src/locales/be/messages.json | 13 +++++++++++++ apps/web/src/locales/bg/messages.json | 13 +++++++++++++ apps/web/src/locales/bn/messages.json | 13 +++++++++++++ apps/web/src/locales/bs/messages.json | 13 +++++++++++++ apps/web/src/locales/ca/messages.json | 13 +++++++++++++ apps/web/src/locales/cs/messages.json | 13 +++++++++++++ apps/web/src/locales/cy/messages.json | 13 +++++++++++++ apps/web/src/locales/da/messages.json | 13 +++++++++++++ apps/web/src/locales/de/messages.json | 13 +++++++++++++ apps/web/src/locales/el/messages.json | 13 +++++++++++++ apps/web/src/locales/en_GB/messages.json | 13 +++++++++++++ apps/web/src/locales/en_IN/messages.json | 13 +++++++++++++ apps/web/src/locales/eo/messages.json | 13 +++++++++++++ apps/web/src/locales/es/messages.json | 13 +++++++++++++ apps/web/src/locales/et/messages.json | 13 +++++++++++++ apps/web/src/locales/eu/messages.json | 13 +++++++++++++ apps/web/src/locales/fa/messages.json | 13 +++++++++++++ apps/web/src/locales/fi/messages.json | 13 +++++++++++++ apps/web/src/locales/fil/messages.json | 13 +++++++++++++ apps/web/src/locales/fr/messages.json | 13 +++++++++++++ apps/web/src/locales/gl/messages.json | 13 +++++++++++++ apps/web/src/locales/he/messages.json | 13 +++++++++++++ apps/web/src/locales/hi/messages.json | 13 +++++++++++++ apps/web/src/locales/hr/messages.json | 13 +++++++++++++ apps/web/src/locales/hu/messages.json | 13 +++++++++++++ apps/web/src/locales/id/messages.json | 13 +++++++++++++ apps/web/src/locales/it/messages.json | 13 +++++++++++++ apps/web/src/locales/ja/messages.json | 13 +++++++++++++ apps/web/src/locales/ka/messages.json | 13 +++++++++++++ apps/web/src/locales/km/messages.json | 13 +++++++++++++ apps/web/src/locales/kn/messages.json | 13 +++++++++++++ apps/web/src/locales/ko/messages.json | 13 +++++++++++++ apps/web/src/locales/lv/messages.json | 13 +++++++++++++ apps/web/src/locales/ml/messages.json | 13 +++++++++++++ apps/web/src/locales/mr/messages.json | 13 +++++++++++++ apps/web/src/locales/my/messages.json | 13 +++++++++++++ apps/web/src/locales/nb/messages.json | 13 +++++++++++++ apps/web/src/locales/ne/messages.json | 13 +++++++++++++ apps/web/src/locales/nl/messages.json | 13 +++++++++++++ apps/web/src/locales/nn/messages.json | 13 +++++++++++++ apps/web/src/locales/or/messages.json | 13 +++++++++++++ apps/web/src/locales/pl/messages.json | 13 +++++++++++++ apps/web/src/locales/pt_BR/messages.json | 13 +++++++++++++ apps/web/src/locales/pt_PT/messages.json | 13 +++++++++++++ apps/web/src/locales/ro/messages.json | 13 +++++++++++++ apps/web/src/locales/ru/messages.json | 13 +++++++++++++ apps/web/src/locales/si/messages.json | 13 +++++++++++++ apps/web/src/locales/sk/messages.json | 13 +++++++++++++ apps/web/src/locales/sl/messages.json | 13 +++++++++++++ apps/web/src/locales/sr/messages.json | 13 +++++++++++++ apps/web/src/locales/sr_CS/messages.json | 13 +++++++++++++ apps/web/src/locales/sv/messages.json | 13 +++++++++++++ apps/web/src/locales/te/messages.json | 13 +++++++++++++ apps/web/src/locales/th/messages.json | 13 +++++++++++++ apps/web/src/locales/tr/messages.json | 13 +++++++++++++ apps/web/src/locales/uk/messages.json | 13 +++++++++++++ apps/web/src/locales/vi/messages.json | 13 +++++++++++++ apps/web/src/locales/zh_CN/messages.json | 17 +++++++++++++++-- apps/web/src/locales/zh_TW/messages.json | 13 +++++++++++++ 62 files changed, 810 insertions(+), 4 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index d1fac1d1357..9e82e573e1a 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index e27a87efa31..94b75a847ed 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 2faf2cb7e12..e26d3b4ee26 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -10285,7 +10285,7 @@ } }, "updatedRevokeSponsorshipConfirmationForSentSponsorship": { - "message": "$EMAIL$ silinsə, bu Ailələr planı üçün sponsorluq istifadə edilə bilməz. Davam etmək istədiyinizə əminsiniz?", + "message": "$EMAIL$ silsəniz, bu Ailələr planı üçün sponsorluq istifadə edilə bilməz. Davam etmək istədiyinizə əminsiniz?", "placeholders": { "email": { "content": "$1", @@ -10294,7 +10294,7 @@ } }, "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship": { - "message": "$EMAIL$ silinsə, bu Ailə planı üçün sponsorluq bitəcək və saxlanılmış ödəniş üsulundan $DATE$ tarixində $40 + müvafiq vergi tutulacaq. $DATE$ tarixinə qədər yeni bir sponsorluq istifadə edə bilməyəcəksiniz. Davam etmək istədiyinizə əminsiniz?", + "message": "$EMAIL$ silsəniz, bu Ailə planı üçün sponsorluq bitəcək və saxlanılmış ödəniş üsulundan $DATE$ tarixində $40 + müvafiq vergi tutulacaq. $DATE$ tarixinə qədər yeni bir sponsorluq istifadə edə bilməyəcəksiniz. Davam etmək istədiyinizə əminsiniz?", "placeholders": { "email": { "content": "$1", @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Ödəniş üsulunuzu əlavə etmək üçün lütfən Paypal ilə ödəniş et düyməsinə klikləyin." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "$EMAIL$ silsəniz, bu Ailə planı üçün sponsorluq bitəcək. Təşkilatınızın daxilindəki bir yer $DATE$ tarixində sponsorlu təşkilatın yenilənmə tarixindən sonra üzvlər və sponsorluqlar üçün əlçatan olacaq.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index c62928d4bdc..b2de9743a23 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 436388652f4..9efefaca41b 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Моля, натиснете бутона за плащане с PayPal, за да добавите платежния си метод." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "Ако премахнете $EMAIL$, спонсорирането на този семеен план ще бъде прекратено. Едно място в организацията ще стане налично за членове или спонсори след датата за подновяване на спонсорирането на организацията – $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 1a8352a6dbf..1bf290fa314 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 9295e7e5610..3b4881e9050 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index d5001893490..de70c3f03ed 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 9db811ff776..8eedab4e393 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Pro přidání způsobu platby klepněte na tlačítko \"Pay with PayPal\"." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "Pokud odeberete $EMAIL$, sponzorství pro tento plán rodiny skončí. Volné místo ve Vaší organizaci bude k dispozici pro členy nebo sponzory po datu obnovení sponzorované organizace na $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 78d1c04a3c9..d823df7f3a0 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index db73ea4784c..cbdf99ad4b7 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index bcf308bedd5..592664e9330 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Bitte klicke auf den Mit PayPal bezahlen Button, um deine Zahlungsmethode hinzuzufügen." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 60aa8db716c..f35efcda015 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 7dd11351f89..e71495d9e7e 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organisation will become available for members or sponsorships after the sponsored organisation renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 5b8f0f521fb..37d7d25bc64 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organisation will become available for members or sponsorships after the sponsored organisation renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 14a15e2965c..01379c904d6 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index b8a11b5e543..86ee310b45d 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 85afcc55be1..606699fe6db 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 5668d987b69..c50cf1776f8 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index b8a71e7b971..06de2bf7bc7 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "لطفاً برای افزودن روش پرداخت خود، روی دکمه پرداخت با پی‌پال کلیک کنید." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 04504a49c2e..4f2394ba2ac 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 8aeb3d17aa1..eb9b572ab98 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index e8016b547f9..f4181fb7cd3 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Veuillez cliquer sur le bouton Payer avec PayPal pour ajouter votre mode de paiement." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "Si vous supprimez $EMAIL$, le parrainage pour ce plan Familles prendra fin. Un siège au sein de votre organisation sera disponible pour les membres ou les parrainages après la date de renouvellement de l'organisation parrainée le $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 004090b4fab..4ac6f383a6d 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 2f11b460d66..9010e380275 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 4fbcada7060..3a1be80c3b0 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 501741f494a..87895b822d6 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 42da598459e..0705a7757a5 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Kattintás a Pay with PayPal gombra a fizetési mód hozzáadásához." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "$EMAIL$ eltávolításával a családi csomag szponzorálása véget ér. A szervezeten belüli hely a szponzorált szervezet megújítási dátuma után válik elérhetővé a tagok vagy a szponzorálások számára: $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 24f095ed094..0cee74fbdc2 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index d1e7f59d3d9..a20d73adb57 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index bc692f4cd78..75a77253542 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 3f6fe55ffe0..56936e9c9a2 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 09873860bce..a58a8b9b519 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 518fe2090df..71111cd0d2e 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 991ae2e31b8..42783abed7c 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 5535e23b030..47ba7175fdd 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Lūgums klikšķināt pogu \"Apmaksāt ar PayPal, lai pievienotu savu maksājumu veidu." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 01c1f6307ee..2e4e47421e1 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 60ddec4332e..53755bd5fcd 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 09873860bce..a58a8b9b519 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 360e59ea70f..4ad9b3f2e77 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 3dc7ab5b476..23f16436a0a 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index fdb5e68d3b4..789dda16cc9 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Klik op \"Pay with PayPal\" voor het toevoegen van je betaalmethode." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "Als je $EMAIL$ verwijdert, zal de sponsoring voor dit Familieplan stoppen. Er komt een zetel beschikbaar binnen je organisatie voor leden of sponsorschap na de vernieuwingsdatum van de gesponsorde organisatie op $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 12c41a6e3db..640bfc5407c 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 09873860bce..a58a8b9b519 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 7480135bc41..6cba19e3224 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Kliknij przycisk Zapłać za pomocą PayPal, aby dodać metodę płatności." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "Jeśli usuniesz $EMAIL$, sponsorowanie tego planu rodzinnego zostanie zakończone. Miejsce w Twojej organizacji stanie się dostępne dla członków lub sponsorów po dacie odnowienia sponsorowanej organizacji w dniu $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 32d060465a5..8ab519d6ceb 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 9aa737e6b23..05f518470fe 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Por favor, clique no botão Pagar com PayPal para adicionar o seu método de pagamento." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "Se remover $EMAIL$, o patrocínio para este plano Familiar termina. Um lugar na sua organização ficará disponível para membros ou patrocínios após a data de renovação da organização patrocinada a $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 8ea595a4a32..6eaa32f5e8a 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index b353a8d0e61..e281e97e24e 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Пожалуйста, нажмите кнопку \"Оплатить с помощью PayPal\", чтобы добавить свой способ оплаты." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "Если вы удалите $EMAIL$, спонсорство по этому семейному плану прекратится. Места в вашей организации станут доступны для участников или спонсорских организаций после даты продления подписки $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 16542445a85..00cc6a041be 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 127e8cce83e..1f0f52b169f 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Pre pridanie platobnej metódy kliknite prosím na Zaplatiť cez PayPal." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 0731d963194..f0c2fc1e65f 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index b698ef5e281..39d095c5d51 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Кликните на Pay with PayPal да бисте додали начин лпаћања." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "Ако уклоните $EMAIL$, спонзорство за овај породични план ће се завршити. Седиште у вашој организацији постаће доступно за чланове или спонзорства након што је спонзорисан датум обнове организације на $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 34282b8aaeb..de3db031c6e 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 43658ca8129..4f468c55979 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 09873860bce..a58a8b9b519 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index cf6aab45cec..9b6828a5dd9 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 4192861d6d1..ab0a4154dc3 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index 00871794ac6..76ab990be23 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Натисніть кнопку Сплатити з PayPal, щоб додати спосіб оплати." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 8218dc06ece..944e3d1183d 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index c126e0c533c..b14d0183433 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -6309,7 +6309,7 @@ "message": "您的组织成员有资格获得免费的 Bitwarden 家庭计划。您可以为不是您的 Bitwarden 组织成员的员工赞助免费 Bitwarden 家庭。赞助非成员需要您的组织内有可用的席位。" }, "sponsoredFamiliesRemoveActiveSponsorship": { - "message": "当您移除某个活动赞助时,该赞助席位将在被赞助组织的续费日期后释放给您的组织使用。" + "message": "当您移除某个活动赞助,在被赞助组织的续费日期之后,您的组织中将释放一个可用的席位。" }, "sponsoredFamiliesEligible": { "message": "您和您的家人有资格获得免费的 Bitwarden 家庭版计划。使用您的个人电子邮箱兑换,即使您不在工作中,也能确保您的数据安全。" @@ -6797,7 +6797,7 @@ "description": "This is used by screen readers to indicate the organization that is currently being shown to the user." }, "accountLoggedInAsName": { - "message": "账户:登录为 $NAME$", + "message": "账户:已登录为 $NAME$", "placeholders": { "name": { "content": "$1", @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "请点击「使用 PayPal 付款」按钮以添加您的付款方式。" + }, + "revokeActiveSponsorshipConfirmation": { + "message": "如果您移除 $EMAIL$,此家庭计划的赞助将结束。在被赞助组织的续费日期 $DATE$ 之后,您的组织中将释放一个可用席位,可供成员或赞助使用。", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 4be5bbcfbd3..c18d71978a6 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -10650,5 +10650,18 @@ }, "clickPayWithPayPal": { "message": "Please click the Pay with PayPal button to add your payment method." + }, + "revokeActiveSponsorshipConfirmation": { + "message": "If you remove $EMAIL$, the sponsorship for this Family plan will end. A seat within your organization will become available for members or sponsorships after the sponsored organization renewal date on $DATE$.", + "placeholders": { + "email": { + "content": "$1", + "example": "user@example.com" + }, + "date": { + "content": "$2", + "example": "12/31/2024" + } + } } } From 79fa246df29c9ed70e0e59d801c0ac1d93ec5e87 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 09:00:06 +0000 Subject: [PATCH 206/245] Autosync the updated translations (#15026) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 4 ++++ apps/browser/src/_locales/az/messages.json | 4 ++++ apps/browser/src/_locales/be/messages.json | 4 ++++ apps/browser/src/_locales/bg/messages.json | 4 ++++ apps/browser/src/_locales/bn/messages.json | 4 ++++ apps/browser/src/_locales/bs/messages.json | 4 ++++ apps/browser/src/_locales/ca/messages.json | 4 ++++ apps/browser/src/_locales/cs/messages.json | 4 ++++ apps/browser/src/_locales/cy/messages.json | 4 ++++ apps/browser/src/_locales/da/messages.json | 4 ++++ apps/browser/src/_locales/de/messages.json | 4 ++++ apps/browser/src/_locales/el/messages.json | 4 ++++ apps/browser/src/_locales/en_GB/messages.json | 4 ++++ apps/browser/src/_locales/en_IN/messages.json | 4 ++++ apps/browser/src/_locales/es/messages.json | 4 ++++ apps/browser/src/_locales/et/messages.json | 4 ++++ apps/browser/src/_locales/eu/messages.json | 14 +++++++++----- apps/browser/src/_locales/fa/messages.json | 4 ++++ apps/browser/src/_locales/fi/messages.json | 4 ++++ apps/browser/src/_locales/fil/messages.json | 4 ++++ apps/browser/src/_locales/fr/messages.json | 4 ++++ apps/browser/src/_locales/gl/messages.json | 4 ++++ apps/browser/src/_locales/he/messages.json | 6 +++++- apps/browser/src/_locales/hi/messages.json | 4 ++++ apps/browser/src/_locales/hr/messages.json | 4 ++++ apps/browser/src/_locales/hu/messages.json | 4 ++++ apps/browser/src/_locales/id/messages.json | 4 ++++ apps/browser/src/_locales/it/messages.json | 4 ++++ apps/browser/src/_locales/ja/messages.json | 4 ++++ apps/browser/src/_locales/ka/messages.json | 4 ++++ apps/browser/src/_locales/km/messages.json | 4 ++++ apps/browser/src/_locales/kn/messages.json | 4 ++++ apps/browser/src/_locales/ko/messages.json | 4 ++++ apps/browser/src/_locales/lt/messages.json | 4 ++++ apps/browser/src/_locales/lv/messages.json | 4 ++++ apps/browser/src/_locales/ml/messages.json | 4 ++++ apps/browser/src/_locales/mr/messages.json | 4 ++++ apps/browser/src/_locales/my/messages.json | 4 ++++ apps/browser/src/_locales/nb/messages.json | 4 ++++ apps/browser/src/_locales/ne/messages.json | 4 ++++ apps/browser/src/_locales/nl/messages.json | 4 ++++ apps/browser/src/_locales/nn/messages.json | 4 ++++ apps/browser/src/_locales/or/messages.json | 4 ++++ apps/browser/src/_locales/pl/messages.json | 4 ++++ apps/browser/src/_locales/pt_BR/messages.json | 4 ++++ apps/browser/src/_locales/pt_PT/messages.json | 4 ++++ apps/browser/src/_locales/ro/messages.json | 4 ++++ apps/browser/src/_locales/ru/messages.json | 4 ++++ apps/browser/src/_locales/si/messages.json | 4 ++++ apps/browser/src/_locales/sk/messages.json | 4 ++++ apps/browser/src/_locales/sl/messages.json | 4 ++++ apps/browser/src/_locales/sr/messages.json | 4 ++++ apps/browser/src/_locales/sv/messages.json | 4 ++++ apps/browser/src/_locales/te/messages.json | 4 ++++ apps/browser/src/_locales/th/messages.json | 6 +++++- apps/browser/src/_locales/tr/messages.json | 4 ++++ apps/browser/src/_locales/uk/messages.json | 4 ++++ apps/browser/src/_locales/vi/messages.json | 4 ++++ apps/browser/src/_locales/zh_CN/messages.json | 8 ++++++-- apps/browser/src/_locales/zh_TW/messages.json | 4 ++++ 60 files changed, 249 insertions(+), 9 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 426dba6ae2c..0a8ba5b1164 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -2677,6 +2677,10 @@ "message": "كل الإرسالات", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "إخفاء النص بشكل افتراضي" }, diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 03a2aa103c6..a38c590e4d4 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -2677,6 +2677,10 @@ "message": "Bütün \"Send\"lər", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Maksimal müraciət sayına çatıldı", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Mətni ilkin olaraq gizlət" }, diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 4a6c11763af..888569cd588 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -2677,6 +2677,10 @@ "message": "Усе Send’ы", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index b6bb159b354..ac99ce4376e 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -2677,6 +2677,10 @@ "message": "Всички изпращания", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Достигнат е максималният брой достъпвания", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Скриване на текста по подразбиране" }, diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index f58a878f83c..fe386c53e62 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -2677,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 0f064076440..b7982ba4981 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -2677,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 3ea0c595916..005c100f105 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -2677,6 +2677,10 @@ "message": "Tots els Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Amaga el text per defecte" }, diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 354f37268de..6fc2d1ddb34 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -2677,6 +2677,10 @@ "message": "Všechny Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Dosažen maximální počet přístupů", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Ve výchozím nastavení skrýt text" }, diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index b26cbf21019..eec15c8ed9a 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -2677,6 +2677,10 @@ "message": "Pob Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index b64655ab399..066ef9c9d9a 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -2677,6 +2677,10 @@ "message": "Alle Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Skjul tekst som standard" }, diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 34fecaadb72..f9818afe58f 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -2677,6 +2677,10 @@ "message": "Alle Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Text standardmäßig ausblenden" }, diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 3d67010a32d..9d55ed3f0c7 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -2677,6 +2677,10 @@ "message": "Όλα τα Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Απόκρυψη κειμένου από προεπιλογή" }, diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index af82196d643..bc9452a7cad 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -2677,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index d0fc2af6f9b..ef329ac551c 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -2677,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 536131b6b78..7b970f996a9 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -2677,6 +2677,10 @@ "message": "Todos los Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 1c09897c53b..9ca7d15e72a 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -2677,6 +2677,10 @@ "message": "Kõik Sendid", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index cb855a077bc..3c7192e465e 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -17,7 +17,7 @@ "message": "Saioa hasi edo sortu kontu berri bat zure kutxa gotorrera sartzeko." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "Gonbidapena onartua" }, "createAccount": { "message": "Sortu kontua" @@ -150,7 +150,7 @@ "message": "Kopiatu segurtasun-kodea" }, "copyName": { - "message": "Copy name" + "message": "Izena kopiatu" }, "copyCompany": { "message": "Copy company" @@ -2677,6 +2677,10 @@ "message": "Send guztiak", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -5190,13 +5194,13 @@ "message": "Lowercase" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Maiuskulak" }, "generatedPassword": { - "message": "Generated password" + "message": "Sortutako pasahitza" }, "compactMode": { - "message": "Compact mode" + "message": "Modu trinkoa" }, "beta": { "message": "Beta" diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 190af9225a3..c40af49d97c 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -2677,6 +2677,10 @@ "message": "همه ارسال ها", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "متن را به‌صورت پیش‌فرض مخفی کن" }, diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 318b80997c9..c93e64a0443 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -2677,6 +2677,10 @@ "message": "Kaikki Sendit", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Piilota teksti oletuksena" }, diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index e4c2c746f80..6106ca7ed62 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -2677,6 +2677,10 @@ "message": "Lahat ng Mga Padala", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 5ee6102b550..c9dae571046 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -2677,6 +2677,10 @@ "message": "Tous les Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Masquer le texte par défaut" }, diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index fbe708b1b08..ef99d2cb211 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -2677,6 +2677,10 @@ "message": "Todos os Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Ocultar texto por defecto" }, diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index b8f5ec0b0b1..72dcdb70376 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -3,7 +3,7 @@ "message": "Bitwarden" }, "appLogoLabel": { - "message": "Bitwarden logo" + "message": "הלוגו של Bitwarden" }, "extName": { "message": "מנהל הסיסמאות Bitwarden", @@ -2677,6 +2677,10 @@ "message": "כל הסֵנְדים", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "מספר הגישות המרבי הושג", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "הסתר טקסט כברירת מחדל" }, diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index a283bd6f438..06dd2c49390 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -2677,6 +2677,10 @@ "message": "सभी Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index bac097fe112..f94a2e60b79 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -2677,6 +2677,10 @@ "message": "Svi Sendovi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Zadano sakrij tekst" }, diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 0572fc77789..55c2ec433c8 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -2677,6 +2677,10 @@ "message": "Összes Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "A maximális hozzáférések száma elérésre került.", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Szöveg elrejtése alapértelmezetten" }, diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 7f8fea33a2e..ec27c6b6328 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -2677,6 +2677,10 @@ "message": "Semua Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Sembunyikan teks secara bawaan" }, diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index c733192493a..77be022e58c 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -2677,6 +2677,10 @@ "message": "Tutti i Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Nascondi testo come default" }, diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index b4f1fa0132e..e66d0a2454c 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -2677,6 +2677,10 @@ "message": "すべての Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "デフォルトでテキストを隠す" }, diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 21ff426dfc5..afd301305ff 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -2677,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index feb5a7706f3..29223942fd6 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -2677,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 2fa7ea8013e..6065dec254f 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -2677,6 +2677,10 @@ "message": "ಎಲ್ಲಾ ಕಳುಹಿಸುತ್ತದೆ", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 2655ef688e7..ef3402dc496 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -2677,6 +2677,10 @@ "message": "모든 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "기본적으로 텍스트 숨기기" }, diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 07e26a862b3..82952be642c 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -2677,6 +2677,10 @@ "message": "Visi siuntimai", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 5a8a82c16ed..23e6d015853 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -2677,6 +2677,10 @@ "message": "Visi Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Pēc noklusējuma paslēpt tekstu" }, diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index a56fb396435..cb75a35bf7c 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -2677,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 416862859c9..c269bef17b6 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -2677,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index feb5a7706f3..29223942fd6 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -2677,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 504f98fe44a..3f23ead23e6 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -2677,6 +2677,10 @@ "message": "Alle Send-er", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Skjul tekst som standard" }, diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index feb5a7706f3..29223942fd6 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -2677,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 4d60287a78e..b98814df45e 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -2677,6 +2677,10 @@ "message": "Alle Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Maximum aantal keren benaderd", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Tekst standaard verbergen" }, diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index feb5a7706f3..29223942fd6 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -2677,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index feb5a7706f3..29223942fd6 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -2677,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 32d6799493e..80119b2be25 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -2677,6 +2677,10 @@ "message": "Wszystkie wysyłki", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Maksymalna liczba dostępów została osiągnięta", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Domyślnie ukryj tekst" }, diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 7d70debf128..56a4ce3018b 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -2677,6 +2677,10 @@ "message": "Todos os Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Ocultar texto por padrão" }, diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 67db6178ace..50a2aeb20bd 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -2677,6 +2677,10 @@ "message": "Todos os Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Número máximo de acessos atingido", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Ocultar texto por predefinição" }, diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 3ed8e876d6f..375f97ef265 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -2677,6 +2677,10 @@ "message": "Toate Send-urile", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index d7f901bf04f..a6c16ca01ce 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -2677,6 +2677,10 @@ "message": "Все Send’ы", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Достигнут максимум обращений", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Скрыть текст по умолчанию" }, diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 2e20f5dc4ce..1b024ddaae8 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -2677,6 +2677,10 @@ "message": "සියලු යවයි", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 8d6f193168e..d25ea28a614 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -2677,6 +2677,10 @@ "message": "Všetky Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Bol dosiahnutý maximálny počet prístupov", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "V predvolenom nastavení skryť text" }, diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 224f31076b8..703563523ac 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -2677,6 +2677,10 @@ "message": "Vse pošiljke", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 54371488c0d..59d72b93173 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -2677,6 +2677,10 @@ "message": "Све „Send“", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Достигнут максималан број приступа", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Сакриј текст подразумевано" }, diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 10b9d965cc8..1fcd208136e 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -2677,6 +2677,10 @@ "message": "Alla Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index feb5a7706f3..29223942fd6 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -2677,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index f18f2ac9ba9..87e5be28d79 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -3,7 +3,7 @@ "message": "bitwarden" }, "appLogoLabel": { - "message": "Bitwarden logo" + "message": "โลโก้ Bitwarden" }, "extName": { "message": "Bitwarden Password Manager", @@ -2677,6 +2677,10 @@ "message": "Send ทั้งหมด", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index ded4568adaa..a72e3593ff9 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -2677,6 +2677,10 @@ "message": "Tüm Send'ler", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Maksimum erişim sayısına ulaşıldı", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Metni varsayılan olarak gizle" }, diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 952c223f262..01c4fa2d73b 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -2677,6 +2677,10 @@ "message": "Усі відправлення", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Типово приховувати текст" }, diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 4db1394023d..fcbcb8f7b6d 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -2677,6 +2677,10 @@ "message": "Tất cả mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 18cbc9c10d3..e370f7749f6 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -2677,6 +2677,10 @@ "message": "所有 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "已达最大访问次数", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "默认隐藏文本" }, @@ -4558,7 +4562,7 @@ "message": "获取桌面 App" }, "getTheDesktopAppDesc": { - "message": "无需使用浏览器访问您的密码库,在桌面 App 和浏览器扩展中同时设置生物识别解锁,即可实现快速解锁。" + "message": "无需使用浏览器访问您的密码库,然后在桌面 App 和浏览器扩展中同时设置生物识别解锁,即可实现快速解锁。" }, "downloadFromBitwardenNow": { "message": "立即从 bitwarden.com 下载" @@ -4971,7 +4975,7 @@ "message": "自定义超时时间最小为 1 分钟。" }, "additionalContentAvailable": { - "message": "有更多内容可用" + "message": "更多内容可用" }, "fileSavedToDevice": { "message": "文件已保存到设备。可以在设备下载中进行管理。" diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 639469b7cd4..ba8b44dc071 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -2677,6 +2677,10 @@ "message": "所有 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "默認隱藏文字" }, From 4c3c1969b55120244a0739417fe270fd868efc4a Mon Sep 17 00:00:00 2001 From: Github Actions <actions@github.com> Date: Mon, 2 Jun 2025 15:17:52 +0000 Subject: [PATCH 207/245] Bumped client version(s) --- apps/web/package.json | 2 +- package-lock.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 6df2974129e..cbeb012169a 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2025.5.1", + "version": "2025.6.0", "scripts": { "build:oss": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", "build:bit": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/package-lock.json b/package-lock.json index fbb4860eb64..0807ea79775 100644 --- a/package-lock.json +++ b/package-lock.json @@ -248,7 +248,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2025.5.1" + "version": "2025.6.0" }, "libs/admin-console": { "name": "@bitwarden/admin-console", From 37e13050a565c024068aa0e95bf2c537ce47e99b Mon Sep 17 00:00:00 2001 From: Daniel Riera <driera@livefront.com> Date: Mon, 2 Jun 2025 11:24:47 -0400 Subject: [PATCH 208/245] PM-16649 (#14656) --- .../fido2/background/fido2.background.spec.ts | 22 +------------------ .../fido2/background/fido2.background.ts | 13 +---------- .../fido2-page-script-append.mv2.spec.ts | 6 ++--- .../content/fido2-page-script-append.mv2.ts | 17 -------------- .../fido2/enums/fido2-content-script.enum.ts | 1 - .../browser/src/background/main.background.ts | 1 - apps/browser/webpack.config.js | 2 -- libs/common/src/enums/feature-flag.enum.ts | 2 -- 8 files changed, 5 insertions(+), 59 deletions(-) delete mode 100644 apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.ts diff --git a/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts index 144af0c0a35..752851b3d37 100644 --- a/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts @@ -3,7 +3,6 @@ import { BehaviorSubject } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { Fido2ActiveRequestManager } from "@bitwarden/common/platform/abstractions/fido2/fido2-active-request-manager.abstraction"; import { AssertCredentialParams, @@ -60,7 +59,6 @@ describe("Fido2Background", () => { let fido2ClientService!: MockProxy<Fido2ClientService<BrowserFido2ParentWindowReference>>; let vaultSettingsService!: MockProxy<VaultSettingsService>; let scriptInjectorServiceMock!: MockProxy<BrowserScriptInjectorService>; - let configServiceMock!: MockProxy<ConfigService>; let enablePasskeysMock$!: BehaviorSubject<boolean>; let activeAccountStatusMock$: BehaviorSubject<AuthenticationStatus>; let authServiceMock!: MockProxy<AuthService>; @@ -80,7 +78,6 @@ describe("Fido2Background", () => { abortController = mock<AbortController>(); registeredContentScripsMock = mock<browser.contentScripts.RegisteredContentScript>(); scriptInjectorServiceMock = mock<BrowserScriptInjectorService>(); - configServiceMock = mock<ConfigService>(); enablePasskeysMock$ = new BehaviorSubject(true); vaultSettingsService.enablePasskeys$ = enablePasskeysMock$; @@ -95,7 +92,6 @@ describe("Fido2Background", () => { fido2ClientService, vaultSettingsService, scriptInjectorServiceMock, - configServiceMock, authServiceMock, ); fido2Background["abortManager"] = abortManagerMock; @@ -186,7 +182,7 @@ describe("Fido2Background", () => { expect(scriptInjectorServiceMock.inject).toHaveBeenCalledWith({ tabId: tabMock.id, injectDetails: sharedScriptInjectionDetails, - mv2Details: { file: Fido2ContentScript.PageScriptAppend }, + mv2Details: { file: Fido2ContentScript.PageScriptDelayAppend }, mv3Details: { file: Fido2ContentScript.PageScript, world: "MAIN" }, }); expect(scriptInjectorServiceMock.inject).toHaveBeenCalledWith({ @@ -202,22 +198,6 @@ describe("Fido2Background", () => { enablePasskeysMock$.next(true); await flushPromises(); - expect(BrowserApi.registerContentScriptsMv2).toHaveBeenCalledWith({ - js: [ - { file: Fido2ContentScript.PageScriptAppend }, - { file: Fido2ContentScript.ContentScript }, - ], - ...sharedRegistrationOptions, - }); - }); - - it("registers the page-script-delay-append-mv2.js content script when the DelayFido2PageScriptInitWithinMv2 feature flag is enabled", async () => { - configServiceMock.getFeatureFlag.mockResolvedValue(true); - isManifestVersionSpy.mockImplementation((manifestVersion) => manifestVersion === 2); - - enablePasskeysMock$.next(true); - await flushPromises(); - expect(BrowserApi.registerContentScriptsMv2).toHaveBeenCalledWith({ js: [ { file: Fido2ContentScript.PageScriptDelayAppend }, diff --git a/apps/browser/src/autofill/fido2/background/fido2.background.ts b/apps/browser/src/autofill/fido2/background/fido2.background.ts index e20a0584d20..788c98ca85b 100644 --- a/apps/browser/src/autofill/fido2/background/fido2.background.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.ts @@ -5,8 +5,6 @@ import { pairwise } from "rxjs/operators"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { Fido2ActiveRequestManager } from "@bitwarden/common/platform/abstractions/fido2/fido2-active-request-manager.abstraction"; import { AssertCredentialParams, @@ -60,7 +58,6 @@ export class Fido2Background implements Fido2BackgroundInterface { private fido2ClientService: Fido2ClientService<BrowserFido2ParentWindowReference>, private vaultSettingsService: VaultSettingsService, private scriptInjectorService: ScriptInjectorService, - private configService: ConfigService, private authService: AuthService, ) {} @@ -403,14 +400,6 @@ export class Fido2Background implements Fido2BackgroundInterface { * delayed append script if the associated feature flag is enabled. */ private async getFido2PageScriptAppendFileName() { - const shouldDelayInit = await this.configService.getFeatureFlag( - FeatureFlag.DelayFido2PageScriptInitWithinMv2, - ); - - if (shouldDelayInit) { - return Fido2ContentScript.PageScriptDelayAppend; - } - - return Fido2ContentScript.PageScriptAppend; + return Fido2ContentScript.PageScriptDelayAppend; } } diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts index 69e17d26fe5..b444c967080 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts @@ -24,7 +24,7 @@ describe("FIDO2 page-script for manifest v2", () => { // FIXME: Remove when updating file. Eslint update // eslint-disable-next-line @typescript-eslint/no-require-imports - require("./fido2-page-script-append.mv2"); + require("./fido2-page-script-delay-append.mv2.ts"); expect(window.document.createElement).not.toHaveBeenCalled(); }); @@ -37,7 +37,7 @@ describe("FIDO2 page-script for manifest v2", () => { // FIXME: Remove when updating file. Eslint update // eslint-disable-next-line @typescript-eslint/no-require-imports - require("./fido2-page-script-append.mv2"); + require("./fido2-page-script-delay-append.mv2.ts"); expect(window.document.createElement).toHaveBeenCalledWith("script"); expect(chrome.runtime.getURL).toHaveBeenCalledWith(Fido2ContentScript.PageScript); @@ -54,7 +54,7 @@ describe("FIDO2 page-script for manifest v2", () => { // FIXME: Remove when updating file. Eslint update // eslint-disable-next-line @typescript-eslint/no-require-imports - require("./fido2-page-script-append.mv2"); + require("./fido2-page-script-delay-append.mv2.ts"); expect(window.document.createElement).toHaveBeenCalledWith("script"); expect(chrome.runtime.getURL).toHaveBeenCalledWith(Fido2ContentScript.PageScript); diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.ts deleted file mode 100644 index f835d2f175b..00000000000 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * This script handles injection of the FIDO2 override page script into the document. - * This is required for manifest v2, but will be removed when we migrate fully to manifest v3. - */ -(function (globalContext) { - if (globalContext.document.contentType !== "text/html") { - return; - } - - const script = globalContext.document.createElement("script"); - script.src = chrome.runtime.getURL("content/fido2-page-script.js"); - script.async = false; - - const scriptInsertionPoint = - globalContext.document.head || globalContext.document.documentElement; - scriptInsertionPoint.prepend(script); -})(globalThis); diff --git a/apps/browser/src/autofill/fido2/enums/fido2-content-script.enum.ts b/apps/browser/src/autofill/fido2/enums/fido2-content-script.enum.ts index 9d9189c1623..eb20ff5f69c 100644 --- a/apps/browser/src/autofill/fido2/enums/fido2-content-script.enum.ts +++ b/apps/browser/src/autofill/fido2/enums/fido2-content-script.enum.ts @@ -1,6 +1,5 @@ export const Fido2ContentScript = { PageScript: "content/fido2-page-script.js", - PageScriptAppend: "content/fido2-page-script-append-mv2.js", PageScriptDelayAppend: "content/fido2-page-script-delay-append-mv2.js", ContentScript: "content/fido2-content-script.js", } as const; diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index a724f857cd1..6ae6f7f7eb7 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1156,7 +1156,6 @@ export default class MainBackground { this.fido2ClientService, this.vaultSettingsService, this.scriptInjectorService, - this.configService, this.authService, ); diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index 6d9113be7ed..e4f60aaf17a 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -324,8 +324,6 @@ if (manifestVersion == 2) { // Manifest V2 background pages can be run through the regular build pipeline. // Since it's a standard webpage. mainConfig.entry.background = "./src/platform/background.ts"; - mainConfig.entry["content/fido2-page-script-append-mv2"] = - "./src/autofill/fido2/content/fido2-page-script-append.mv2.ts"; mainConfig.entry["content/fido2-page-script-delay-append-mv2"] = "./src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts"; diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 696f7028159..b3b8cc99926 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -21,7 +21,6 @@ export enum FeatureFlag { /* Autofill */ BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain", - DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2", EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill", IdpAutoSubmitLogin = "idp-auto-submit-login", NotificationRefresh = "notification-refresh", @@ -87,7 +86,6 @@ export const DefaultFeatureFlagValue = { /* Autofill */ [FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE, - [FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE, [FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE, [FeatureFlag.IdpAutoSubmitLogin]: FALSE, [FeatureFlag.NotificationRefresh]: FALSE, From c215fac81874c007be8c299bb91d244b8b3a81ec Mon Sep 17 00:00:00 2001 From: Bryan Cunningham <bcunningham@bitwarden.com> Date: Mon, 2 Jun 2025 12:05:30 -0400 Subject: [PATCH 209/245] [CL-703] Use logical css properties in CL components (#14951) * update usage of margin-right with margin-inline-end * update usage of margin-left with margin-inline-start * update usage of paddiing-right with padding-inline-end * update usage of paddiing-left with padding-inline-start * update usage of radius to use logical properties --- .../src/async-actions/in-forms.stories.ts | 10 +++++----- .../src/async-actions/standalone.stories.ts | 2 +- .../src/banner/banner.component.html | 2 +- .../src/breadcrumbs/breadcrumb.component.html | 2 +- libs/components/src/button/button.stories.ts | 18 +++++++++--------- .../src/callout/callout.component.html | 2 +- .../src/checkbox/checkbox.component.ts | 2 +- .../src/checkbox/checkbox.stories.ts | 8 ++++---- .../src/chip-select/chip-select.component.html | 4 ++-- .../src/dialog/dialog/dialog.stories.ts | 4 ++-- .../src/drawer/drawer-header.component.ts | 2 +- .../form-control/form-control.component.html | 2 +- .../src/form-control/form-control.component.ts | 2 +- .../src/form-field/form-field.component.html | 16 ++++++++-------- .../src/layout/layout.component.html | 2 +- libs/components/src/link/link.stories.ts | 6 +++--- .../multi-select/multi-select.component.html | 6 +++--- .../src/navigation/nav-group.component.html | 2 +- .../src/navigation/nav-item.component.html | 4 ++-- .../src/navigation/nav-item.stories.ts | 4 ++-- .../src/popover/popover.component.html | 2 +- libs/components/src/popover/popover.stories.ts | 6 +++--- .../src/progress/progress.component.html | 2 +- .../src/radio-button/radio-input.component.ts | 2 +- .../src/search/search.component.html | 2 +- .../src/select/select.component.html | 2 +- .../components/kitchen-sink-form.component.ts | 2 +- .../components/src/table/sortable.component.ts | 2 +- .../src/tabs/shared/tab-header.component.ts | 2 +- .../tabs/tab-nav-bar/tab-link.component.html | 2 +- libs/components/src/tabs/tabs.stories.ts | 4 ++-- libs/components/src/toast/toast.component.html | 2 +- .../src/toggle-group/toggle.component.ts | 4 ++-- 33 files changed, 67 insertions(+), 67 deletions(-) diff --git a/libs/components/src/async-actions/in-forms.stories.ts b/libs/components/src/async-actions/in-forms.stories.ts index b45f750084c..857a23227f5 100644 --- a/libs/components/src/async-actions/in-forms.stories.ts +++ b/libs/components/src/async-actions/in-forms.stories.ts @@ -30,11 +30,11 @@ const template = ` <button type="button" bitSuffix bitIconButton="bwi-refresh" bitFormButton [bitAction]="refresh"></button> </bit-form-field> - <button class="tw-mr-2" type="submit" buttonType="primary" bitButton bitFormButton>Submit</button> - <button class="tw-mr-2" type="button" buttonType="secondary" bitButton bitFormButton>Cancel</button> - <button class="tw-mr-2" type="button" buttonType="danger" bitButton bitFormButton [bitAction]="delete">Delete</button> - <button class="tw-mr-2" type="button" buttonType="secondary" bitButton bitFormButton [disabled]="true">Disabled</button> - <button class="tw-mr-2" type="button" buttonType="secondary" bitIconButton="bwi-star" bitFormButton [bitAction]="delete">Delete</button> + <button class="tw-me-2" type="submit" buttonType="primary" bitButton bitFormButton>Submit</button> + <button class="tw-me-2" type="button" buttonType="secondary" bitButton bitFormButton>Cancel</button> + <button class="tw-me-2" type="button" buttonType="danger" bitButton bitFormButton [bitAction]="delete">Delete</button> + <button class="tw-me-2" type="button" buttonType="secondary" bitButton bitFormButton [disabled]="true">Disabled</button> + <button class="tw-me-2" type="button" buttonType="secondary" bitIconButton="bwi-star" bitFormButton [bitAction]="delete">Delete</button> </form>`; @Component({ diff --git a/libs/components/src/async-actions/standalone.stories.ts b/libs/components/src/async-actions/standalone.stories.ts index 52b85b88561..d6f7f978bd5 100644 --- a/libs/components/src/async-actions/standalone.stories.ts +++ b/libs/components/src/async-actions/standalone.stories.ts @@ -12,7 +12,7 @@ import { IconButtonModule } from "../icon-button"; import { BitActionDirective } from "./bit-action.directive"; const template = /*html*/ ` - <button bitButton buttonType="primary" [bitAction]="action" class="tw-mr-2"> + <button bitButton buttonType="primary" [bitAction]="action" class="tw-me-2"> Perform action {{ statusEmoji }} </button> <button bitIconButton="bwi-trash" buttonType="danger" [bitAction]="action"></button>`; diff --git a/libs/components/src/banner/banner.component.html b/libs/components/src/banner/banner.component.html index 1a9d58d342a..6f271d587b5 100644 --- a/libs/components/src/banner/banner.component.html +++ b/libs/components/src/banner/banner.component.html @@ -1,5 +1,5 @@ <div - class="tw-flex tw-items-center tw-gap-2 tw-p-2 tw-pl-4 tw-text-main tw-border-transparent tw-bg-clip-padding tw-border-solid tw-border-b tw-border-0" + class="tw-flex tw-items-center tw-gap-2 tw-p-2 tw-ps-4 tw-text-main tw-border-transparent tw-bg-clip-padding tw-border-solid tw-border-b tw-border-0" [ngClass]="bannerClass" [attr.role]="useAlertRole ? 'status' : null" [attr.aria-live]="useAlertRole ? 'polite' : null" diff --git a/libs/components/src/breadcrumbs/breadcrumb.component.html b/libs/components/src/breadcrumbs/breadcrumb.component.html index bb4dc7cdffe..28a93134496 100644 --- a/libs/components/src/breadcrumbs/breadcrumb.component.html +++ b/libs/components/src/breadcrumbs/breadcrumb.component.html @@ -1,6 +1,6 @@ <ng-template> @if (icon) { - <i class="bwi {{ icon }} !tw-mr-2" aria-hidden="true"></i> + <i class="bwi {{ icon }} !tw-me-2" aria-hidden="true"></i> } <ng-content></ng-content> </ng-template> diff --git a/libs/components/src/button/button.stories.ts b/libs/components/src/button/button.stories.ts index d0a4354f374..29a9e367fcc 100644 --- a/libs/components/src/button/button.stories.ts +++ b/libs/components/src/button/button.stories.ts @@ -88,13 +88,13 @@ export const DisabledWithAttribute: Story = { props: args, template: ` @if (disabled) { - <button bitButton disabled [loading]="loading" [block]="block" buttonType="primary" class="tw-mr-2">Primary</button> - <button bitButton disabled [loading]="loading" [block]="block" buttonType="secondary" class="tw-mr-2">Secondary</button> - <button bitButton disabled [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button> + <button bitButton disabled [loading]="loading" [block]="block" buttonType="primary" class="tw-me-2">Primary</button> + <button bitButton disabled [loading]="loading" [block]="block" buttonType="secondary" class="tw-me-2">Secondary</button> + <button bitButton disabled [loading]="loading" [block]="block" buttonType="danger" class="tw-me-2">Danger</button> } @else { - <button bitButton [loading]="loading" [block]="block" buttonType="primary" class="tw-mr-2">Primary</button> - <button bitButton [loading]="loading" [block]="block" buttonType="secondary" class="tw-mr-2">Secondary</button> - <button bitButton [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button> + <button bitButton [loading]="loading" [block]="block" buttonType="primary" class="tw-me-2">Primary</button> + <button bitButton [loading]="loading" [block]="block" buttonType="secondary" class="tw-me-2">Secondary</button> + <button bitButton [loading]="loading" [block]="block" buttonType="danger" class="tw-me-2">Danger</button> } `, }), @@ -110,10 +110,10 @@ export const Block: Story = { template: ` <span class="tw-flex"> <button bitButton [buttonType]="buttonType" [block]="block">[block]="true" Button</button> - <a bitButton [buttonType]="buttonType" [block]="block" href="#" class="tw-ml-2">[block]="true" Link</a> + <a bitButton [buttonType]="buttonType" [block]="block" href="#" class="tw-ms-2">[block]="true" Link</a> - <button bitButton [buttonType]="buttonType" block class="tw-ml-2">block Button</button> - <a bitButton [buttonType]="buttonType" block href="#" class="tw-ml-2">block Link</a> + <button bitButton [buttonType]="buttonType" block class="tw-ms-2">block Button</button> + <a bitButton [buttonType]="buttonType" block href="#" class="tw-ms-2">block Link</a> </span> `, }), diff --git a/libs/components/src/callout/callout.component.html b/libs/components/src/callout/callout.component.html index 4e7b5f2a0cc..509d14188ca 100644 --- a/libs/components/src/callout/callout.component.html +++ b/libs/components/src/callout/callout.component.html @@ -1,5 +1,5 @@ <aside - class="tw-mb-4 tw-box-border tw-rounded-lg tw-border tw-border-l-4 tw-border-solid tw-bg-background tw-pl-3 tw-pr-2 tw-py-2 tw-leading-5 tw-text-main" + class="tw-mb-4 tw-box-border tw-rounded-lg tw-border tw-border-l-4 tw-border-solid tw-bg-background tw-ps-3 tw-pe-2 tw-py-2 tw-leading-5 tw-text-main" [ngClass]="calloutClass" [attr.aria-labelledby]="titleId" > diff --git a/libs/components/src/checkbox/checkbox.component.ts b/libs/components/src/checkbox/checkbox.component.ts index 0ce6f1889b5..05993ee4e7a 100644 --- a/libs/components/src/checkbox/checkbox.component.ts +++ b/libs/components/src/checkbox/checkbox.component.ts @@ -27,7 +27,7 @@ export class CheckboxComponent implements BitFormControlAbstraction { "tw-border-secondary-500", "tw-h-[1.12rem]", "tw-w-[1.12rem]", - "tw-mr-1.5", + "tw-me-1.5", "tw-flex-none", // Flexbox fix for bit-form-control "before:tw-content-['']", diff --git a/libs/components/src/checkbox/checkbox.stories.ts b/libs/components/src/checkbox/checkbox.stories.ts index 9a59897e009..123c6704ff4 100644 --- a/libs/components/src/checkbox/checkbox.stories.ts +++ b/libs/components/src/checkbox/checkbox.stories.ts @@ -197,15 +197,15 @@ export const Custom: Story = { <div class="tw-flex tw-flex-col tw-w-32"> <label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2"> A-Z - <input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox /> + <input class="tw-ms-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox /> </label> <label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2"> a-z - <input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox /> + <input class="tw-ms-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox /> </label> <label class="tw-text-main tw-flex tw-bg-secondary-300 tw-p-2"> 0-9 - <input class="tw-ml-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox /> + <input class="tw-ms-auto focus-visible:tw-ring-offset-secondary-300" type="checkbox" bitCheckbox /> </label> </div> `, @@ -232,7 +232,7 @@ export const InTableRow: Story = { type="checkbox" bitCheckbox id="checkAll" - class="tw-mr-2" + class="tw-me-2" /> <label for="checkAll" class="tw-mb-0"> All diff --git a/libs/components/src/chip-select/chip-select.component.html b/libs/components/src/chip-select/chip-select.component.html index ff561ef8403..78321afa9b9 100644 --- a/libs/components/src/chip-select/chip-select.component.html +++ b/libs/components/src/chip-select/chip-select.component.html @@ -14,7 +14,7 @@ <!-- Primary button --> <button type="button" - class="tw-inline-flex tw-gap-1.5 tw-items-center tw-justify-between tw-bg-transparent hover:tw-bg-transparent tw-border-none tw-outline-none tw-w-full tw-py-1 tw-pl-3 last:tw-pr-3 [&:not(:last-child)]:tw-pr-0 tw-truncate tw-text-[color:inherit] tw-text-[length:inherit]" + class="tw-inline-flex tw-gap-1.5 tw-items-center tw-justify-between tw-bg-transparent hover:tw-bg-transparent tw-border-none tw-outline-none tw-w-full tw-py-1 tw-ps-3 last:tw-pe-3 [&:not(:last-child)]:tw-pe-0 tw-truncate tw-text-[color:inherit] tw-text-[length:inherit]" data-fvw-target [ngClass]="{ 'tw-cursor-not-allowed': disabled, @@ -45,7 +45,7 @@ type="button" [attr.aria-label]="'removeItem' | i18n: label" [disabled]="disabled" - class="tw-bg-transparent hover:tw-bg-transparent tw-outline-none tw-rounded-full tw-py-0.5 tw-px-1 tw-mr-1 tw-text-[color:inherit] tw-text-[length:inherit] tw-border-solid tw-border tw-border-transparent hover:tw-border-text-contrast hover:disabled:tw-border-transparent tw-flex tw-items-center tw-justify-center focus-visible:tw-ring-2 tw-ring-text-contrast focus-visible:hover:tw-border-transparent" + class="tw-bg-transparent hover:tw-bg-transparent tw-outline-none tw-rounded-full tw-py-0.5 tw-px-1 tw-me-1 tw-text-[color:inherit] tw-text-[length:inherit] tw-border-solid tw-border tw-border-transparent hover:tw-border-text-contrast hover:disabled:tw-border-transparent tw-flex tw-items-center tw-justify-center focus-visible:tw-ring-2 tw-ring-text-contrast focus-visible:hover:tw-border-transparent" [ngClass]="{ 'tw-cursor-not-allowed': disabled, }" diff --git a/libs/components/src/dialog/dialog/dialog.stories.ts b/libs/components/src/dialog/dialog/dialog.stories.ts index 03a88458f5a..bb8b2450de2 100644 --- a/libs/components/src/dialog/dialog/dialog.stories.ts +++ b/libs/components/src/dialog/dialog/dialog.stories.ts @@ -89,7 +89,7 @@ export const Default: Story = { <button bitButton buttonType="secondary" [disabled]="loading">Cancel</button> <button [disabled]="loading" - class="tw-ml-auto" + class="tw-ms-auto" bitIconButton="bwi-trash" buttonType="danger" size="default" @@ -252,7 +252,7 @@ export const WithCards: Story = { <button bitButton buttonType="secondary" [disabled]="loading">Cancel</button> <button [disabled]="loading" - class="tw-ml-auto" + class="tw-ms-auto" bitIconButton="bwi-trash" buttonType="danger" size="default" diff --git a/libs/components/src/drawer/drawer-header.component.ts b/libs/components/src/drawer/drawer-header.component.ts index de112a448cf..c78a9020200 100644 --- a/libs/components/src/drawer/drawer-header.component.ts +++ b/libs/components/src/drawer/drawer-header.component.ts @@ -18,7 +18,7 @@ import { DrawerCloseDirective } from "./drawer-close.directive"; imports: [CommonModule, DrawerCloseDirective, TypographyModule, IconButtonModule, I18nPipe], templateUrl: "drawer-header.component.html", host: { - class: "tw-block tw-pl-4 tw-pr-2 tw-py-2", + class: "tw-block tw-ps-4 tw-pe-2 tw-py-2", }, }) export class DrawerHeaderComponent { diff --git a/libs/components/src/form-control/form-control.component.html b/libs/components/src/form-control/form-control.component.html index cc9c3dabbb6..735e375a29a 100644 --- a/libs/components/src/form-control/form-control.component.html +++ b/libs/components/src/form-control/form-control.component.html @@ -21,7 +21,7 @@ </span> </label> @if (hasError) { - <div class="tw-mt-1 tw-text-danger tw-text-xs tw-ml-0.5"> + <div class="tw-mt-1 tw-text-danger tw-text-xs tw-ms-0.5"> <i class="bwi bwi-error"></i> {{ displayError }} </div> } diff --git a/libs/components/src/form-control/form-control.component.ts b/libs/components/src/form-control/form-control.component.ts index 690c00a9dc0..c59536e2410 100644 --- a/libs/components/src/form-control/form-control.component.ts +++ b/libs/components/src/form-control/form-control.component.ts @@ -40,7 +40,7 @@ export class FormControlComponent { @HostBinding("class") get classes() { return [] - .concat(this.inline ? ["tw-inline-block", "tw-mr-4"] : ["tw-block"]) + .concat(this.inline ? ["tw-inline-block", "tw-me-4"] : ["tw-block"]) .concat(this.disableMargin ? [] : ["tw-mb-4"]); } diff --git a/libs/components/src/form-field/form-field.component.html b/libs/components/src/form-field/form-field.component.html index 02d7c37cadf..c4fd018b3ba 100644 --- a/libs/components/src/form-field/form-field.component.html +++ b/libs/components/src/form-field/form-field.component.html @@ -20,7 +20,7 @@ <div class="tw-absolute tw-size-full tw-top-0 tw-pointer-events-none tw-z-20"> <div class="tw-size-full tw-flex"> <div - class="tw-min-w-3 tw-border-r-0 group-focus-within/bit-form-field:tw-border-r-0 !tw-rounded-l-lg" + class="tw-min-w-3 tw-border-r-0 group-focus-within/bit-form-field:tw-border-r-0 !tw-rounded-s-lg" [ngClass]="inputBorderClasses" ></div> <div @@ -40,7 +40,7 @@ </label> </div> <div - class="tw-min-w-3 tw-grow tw-border-l-0 group-focus-within/bit-form-field:tw-border-l-0 !tw-rounded-r-lg" + class="tw-min-w-3 tw-grow tw-border-l-0 group-focus-within/bit-form-field:tw-border-l-0 !tw-rounded-e-lg" [ngClass]="inputBorderClasses" ></div> </div> @@ -50,7 +50,7 @@ > <div #prefixContainer - class="tw-flex tw-items-center tw-gap-1 tw-pl-3 tw-py-2" + class="tw-flex tw-items-center tw-gap-1 tw-ps-3 tw-py-2" [hidden]="!prefixHasChildren()" > <ng-container *ngTemplateOutlet="prefixContent"></ng-container> @@ -59,15 +59,15 @@ class="tw-w-full tw-relative tw-py-2 has-[bit-select]:tw-p-0 has-[bit-multi-select]:tw-p-0 has-[input:read-only:not([hidden])]:tw-bg-secondary-100 has-[textarea:read-only:not([hidden])]:tw-bg-secondary-100" data-default-content [ngClass]="[ - prefixHasChildren() ? '' : 'tw-rounded-l-lg tw-pl-3', - suffixHasChildren() ? '' : 'tw-rounded-r-lg tw-pr-3', + prefixHasChildren() ? '' : 'tw-rounded-s-lg tw-ps-3', + suffixHasChildren() ? '' : 'tw-rounded-e-lg tw-pe-3', ]" > <ng-container *ngTemplateOutlet="defaultContent"></ng-container> </div> <div #suffixContainer - class="tw-flex tw-items-center tw-gap-1 tw-pr-3 tw-py-2" + class="tw-flex tw-items-center tw-gap-1 tw-pe-3 tw-py-2" [hidden]="!suffixHasChildren()" > <ng-container *ngTemplateOutlet="suffixContent"></ng-container> @@ -92,7 +92,7 @@ <div #prefixContainer [hidden]="!prefixHasChildren()" - class="tw-flex tw-items-center tw-gap-1 tw-pl-1" + class="tw-flex tw-items-center tw-gap-1 tw-ps-1" > <ng-container *ngTemplateOutlet="prefixContent"></ng-container> </div> @@ -105,7 +105,7 @@ <div #suffixContainer [hidden]="!suffixHasChildren()" - class="tw-flex tw-items-center tw-gap-1 tw-pr-1" + class="tw-flex tw-items-center tw-gap-1 tw-pe-1" > <ng-container *ngTemplateOutlet="suffixContent"></ng-container> </div> diff --git a/libs/components/src/layout/layout.component.html b/libs/components/src/layout/layout.component.html index 33b8de81572..f4b0a09db1e 100644 --- a/libs/components/src/layout/layout.component.html +++ b/libs/components/src/layout/layout.component.html @@ -18,7 +18,7 @@ <main [id]="mainContentId" tabindex="-1" - class="tw-overflow-auto tw-min-w-0 tw-flex-1 tw-bg-background tw-p-6 md:tw-ml-0 tw-ml-16" + class="tw-overflow-auto tw-min-w-0 tw-flex-1 tw-bg-background tw-p-6 md:tw-ms-0 tw-ms-16" > <ng-content></ng-content> diff --git a/libs/components/src/link/link.stories.ts b/libs/components/src/link/link.stories.ts index edf2cb14cd6..6a0be5499dd 100644 --- a/libs/components/src/link/link.stories.ts +++ b/libs/components/src/link/link.stories.ts @@ -147,10 +147,10 @@ export const Disabled: Story = { render: (args) => ({ props: args, template: /*html*/ ` - <button bitLink disabled linkType="primary" class="tw-mr-2">Primary</button> - <button bitLink disabled linkType="secondary" class="tw-mr-2">Secondary</button> + <button bitLink disabled linkType="primary" class="tw-me-2">Primary</button> + <button bitLink disabled linkType="secondary" class="tw-me-2">Secondary</button> <div class="tw-bg-primary-600 tw-p-2 tw-inline-block"> - <button bitLink disabled linkType="contrast" class="tw-mr-2">Contrast</button> + <button bitLink disabled linkType="contrast">Contrast</button> </div> `, }), diff --git a/libs/components/src/multi-select/multi-select.component.html b/libs/components/src/multi-select/multi-select.component.html index e157871e17a..0b46ef2662d 100644 --- a/libs/components/src/multi-select/multi-select.component.html +++ b/libs/components/src/multi-select/multi-select.component.html @@ -20,14 +20,14 @@ appendTo="body" > <ng-template ng-loadingspinner-tmp> - <i class="bwi bwi-spinner bwi-spin tw-mr-1" [title]="loadingText" aria-hidden="true"></i> + <i class="bwi bwi-spinner bwi-spin tw-me-1" [title]="loadingText" aria-hidden="true"></i> </ng-template> <ng-template ng-label-tmp let-item="item" let-clear="clear"> <button type="button" bitBadge variant="primary" - class="tw-mr-1 disabled:tw-border-0 tw-flex tw-gap-1.5 tw-items-center" + class="tw-me-1 disabled:tw-border-0 tw-flex tw-gap-1.5 tw-items-center" [disabled]="disabled" (click)="clear(item)" > @@ -47,7 +47,7 @@ <i class="bwi bwi-fw bwi-check" aria-hidden="true"></i> } </div> - <div class="tw-mr-2 tw-flex-initial"> + <div class="tw-me-2 tw-flex-initial"> @if (item.icon != null) { <i class="bwi bwi-fw {{ item.icon }}" aria-hidden="true"></i> } diff --git a/libs/components/src/navigation/nav-group.component.html b/libs/components/src/navigation/nav-group.component.html index 9752fe56eb1..cbb270ad070 100644 --- a/libs/components/src/navigation/nav-group.component.html +++ b/libs/components/src/navigation/nav-group.component.html @@ -15,7 +15,7 @@ <ng-template #button> <button type="button" - class="tw-ml-auto" + class="tw-ms-auto" [bitIconButton]=" open ? 'bwi-angle-up' : variant === 'tree' ? 'bwi-angle-right' : 'bwi-angle-down' " diff --git a/libs/components/src/navigation/nav-item.component.html b/libs/components/src/navigation/nav-item.component.html index 595ddf5a99f..f86f7e2c6d7 100644 --- a/libs/components/src/navigation/nav-item.component.html +++ b/libs/components/src/navigation/nav-item.component.html @@ -52,7 +52,7 @@ class="tw-truncate" [ngClass]="[ variant === 'tree' ? 'tw-py-1' : 'tw-py-2', - data.open ? 'tw-pr-4' : 'tw-text-center', + data.open ? 'tw-pe-4' : 'tw-text-center', ]" > <i @@ -103,7 +103,7 @@ <div *ngIf="data.open" - class="tw-flex -tw-ml-3 tw-pr-4 tw-gap-1 [&>*:focus-visible::before]:!tw-ring-text-alt2 [&>*:hover]:!tw-border-text-alt2 [&>*]:tw-text-alt2 empty:tw-hidden" + class="tw-flex -tw-ms-3 tw-pe-4 tw-gap-1 [&>*:focus-visible::before]:!tw-ring-text-alt2 [&>*:hover]:!tw-border-text-alt2 [&>*]:tw-text-alt2 empty:tw-hidden" [ngClass]="[variant === 'tree' ? 'tw-py-1' : 'tw-py-2']" > <ng-content select="[slot=end]"></ng-content> diff --git a/libs/components/src/navigation/nav-item.stories.ts b/libs/components/src/navigation/nav-item.stories.ts index 7d24090f06d..09642fd2b1c 100644 --- a/libs/components/src/navigation/nav-item.stories.ts +++ b/libs/components/src/navigation/nav-item.stories.ts @@ -83,7 +83,7 @@ export const WithChildButtons: Story = { <bit-nav-item text="Hello World" [route]="['']" icon="bwi-collection-shared"> <button slot="end" - class="tw-ml-auto" + class="tw-ms-auto" [bitIconButton]="'bwi-pencil-square'" [buttonType]="'light'" size="small" @@ -91,7 +91,7 @@ export const WithChildButtons: Story = { ></button> <button slot="end" - class="tw-ml-auto" + class="tw-ms-auto" [bitIconButton]="'bwi-check'" [buttonType]="'light'" size="small" diff --git a/libs/components/src/popover/popover.component.html b/libs/components/src/popover/popover.component.html index cb0681822d0..05db9112b8a 100644 --- a/libs/components/src/popover/popover.component.html +++ b/libs/components/src/popover/popover.component.html @@ -4,7 +4,7 @@ <div class="tw-relative tw-z-20 tw-w-72 tw-break-words tw-bg-background tw-pb-4 tw-pt-2 tw-text-main" > - <div class="tw-mb-1 tw-mr-2 tw-flex tw-items-start tw-justify-between tw-gap-4 tw-pl-4"> + <div class="tw-mb-1 tw-me-2 tw-flex tw-items-start tw-justify-between tw-gap-4 tw-ps-4"> <h2 bitTypography="h5" class="tw-mt-1 tw-font-semibold"> {{ title }} </h2> diff --git a/libs/components/src/popover/popover.stories.ts b/libs/components/src/popover/popover.stories.ts index 8e276afa0c4..ee387d69e56 100644 --- a/libs/components/src/popover/popover.stories.ts +++ b/libs/components/src/popover/popover.stories.ts @@ -62,7 +62,7 @@ type Story = StoryObj<PopoverTriggerForDirective>; const popoverContent = ` <bit-popover [title]="'Example Title'" #myPopover> <div>Lorem ipsum dolor <a href="#">adipisicing elit</a>.</div> - <ul class="tw-mt-2 tw-mb-0 tw-pl-4"> + <ul class="tw-mt-2 tw-mb-0 tw-ps-4"> <li>Dolor sit amet consectetur</li> <li>Esse labore veniam tempora</li> <li>Adipisicing elit ipsum <a href="#">iustolaborum</a></li> @@ -96,7 +96,7 @@ export const Open: Story = { template: ` <bit-popover [title]="'Example Title'" #myPopover="popoverComponent"> <div>Lorem ipsum dolor <a href="#">adipisicing elit</a>.</div> - <ul class="tw-mt-2 tw-mb-0 tw-pl-4"> + <ul class="tw-mt-2 tw-mb-0 tw-ps-4"> <li>Dolor sit amet consectetur</li> <li>Esse labore veniam tempora</li> <li>Adipisicing elit ipsum <a href="#">iustolaborum</a></li> @@ -118,7 +118,7 @@ export const OpenLongTitle: Story = { template: ` <bit-popover [title]="'Example Title that is really long it wraps 2 lines'" #myPopover="popoverComponent"> <div>Lorem ipsum dolor <a href="#">adipisicing elit</a>.</div> - <ul class="tw-mt-2 tw-mb-0 tw-pl-4"> + <ul class="tw-mt-2 tw-mb-0 tw-ps-4"> <li>Dolor sit amet consectetur</li> <li>Esse labore veniam tempora</li> <li>Adipisicing elit ipsum <a href="#">iustolaborum</a></li> diff --git a/libs/components/src/progress/progress.component.html b/libs/components/src/progress/progress.component.html index 30b68d9d645..21b047f732a 100644 --- a/libs/components/src/progress/progress.component.html +++ b/libs/components/src/progress/progress.component.html @@ -11,7 +11,7 @@ <div class="tw-flex tw-h-full tw-flex-wrap tw-items-center tw-overflow-hidden"> <!-- If text is too long to fit, wrap it below to hide --> <div class="tw-h-full"> </div> - <div class="tw-pr-1">{{ textContent }}</div> + <div class="tw-pe-1">{{ textContent }}</div> </div> } </div> diff --git a/libs/components/src/radio-button/radio-input.component.ts b/libs/components/src/radio-button/radio-input.component.ts index 4a9f5dede60..5473f70394e 100644 --- a/libs/components/src/radio-button/radio-input.component.ts +++ b/libs/components/src/radio-button/radio-input.component.ts @@ -30,7 +30,7 @@ export class RadioInputComponent implements BitFormControlAbstraction { "tw-border-secondary-600", "tw-w-[1.12rem]", "tw-h-[1.12rem]", - "tw-mr-1.5", + "tw-me-1.5", "tw-flex-none", // Flexbox fix for bit-form-control "hover:tw-border-2", diff --git a/libs/components/src/search/search.component.html b/libs/components/src/search/search.component.html index 5bb25425e57..759e25c07dd 100644 --- a/libs/components/src/search/search.component.html +++ b/libs/components/src/search/search.component.html @@ -13,7 +13,7 @@ [type]="inputType" [id]="id" [placeholder]="placeholder ?? ('search' | i18n)" - class="tw-pl-9" + class="tw-ps-9" [ngModel]="searchText" (ngModelChange)="onChange($event)" (blur)="onTouch()" diff --git a/libs/components/src/select/select.component.html b/libs/components/src/select/select.component.html index dcca7ae195e..84de9827b97 100644 --- a/libs/components/src/select/select.component.html +++ b/libs/components/src/select/select.component.html @@ -12,7 +12,7 @@ > <ng-template ng-option-tmp let-item="item"> <div class="tw-flex" [title]="item.label"> - <div class="tw-mr-2 tw-flex-initial"> + <div class="tw-me-2 tw-flex-initial"> @if (item.icon != null) { <i class="bwi bwi-fw {{ item.icon }}" aria-hidden="true"></i> } diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts index 5fc01d37d53..babc4365c8e 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts @@ -117,7 +117,7 @@ import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; <bit-popover [title]="'Password help'" #myPopover> <div>A strong password has the following:</div> - <ul class="tw-mt-2 tw-mb-0 tw-pl-4"> + <ul class="tw-mt-2 tw-mb-0 tw-ps-4"> <li>Letters</li> <li>Numbers</li> <li>Special characters</li> diff --git a/libs/components/src/table/sortable.component.ts b/libs/components/src/table/sortable.component.ts index bdfb87ac52f..1d2a2c07d6f 100644 --- a/libs/components/src/table/sortable.component.ts +++ b/libs/components/src/table/sortable.component.ts @@ -17,7 +17,7 @@ import { TableComponent } from "./table.component"; (click)="setActive()" > <ng-content></ng-content> - <i class="bwi tw-ml-2" [ngClass]="icon"></i> + <i class="bwi tw-ms-2" [ngClass]="icon"></i> </button> `, standalone: true, diff --git a/libs/components/src/tabs/shared/tab-header.component.ts b/libs/components/src/tabs/shared/tab-header.component.ts index c45bafb3d52..077ee2b8aa6 100644 --- a/libs/components/src/tabs/shared/tab-header.component.ts +++ b/libs/components/src/tabs/shared/tab-header.component.ts @@ -7,7 +7,7 @@ import { Component } from "@angular/core"; selector: "bit-tab-header", host: { class: - "tw-h-16 tw-pl-4 tw-bg-background-alt tw-flex tw-items-end tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300", + "tw-h-16 tw-ps-4 tw-bg-background-alt tw-flex tw-items-end tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300", }, template: `<ng-content></ng-content>`, standalone: true, diff --git a/libs/components/src/tabs/tab-nav-bar/tab-link.component.html b/libs/components/src/tabs/tab-nav-bar/tab-link.component.html index 0b5a653d966..f1265c3de5d 100644 --- a/libs/components/src/tabs/tab-nav-bar/tab-link.component.html +++ b/libs/components/src/tabs/tab-nav-bar/tab-link.component.html @@ -14,7 +14,7 @@ <div class="group-hover/tab:tw-underline"> <ng-content></ng-content> </div> - <div class="tw-font-normal tw-ml-2 empty:tw-ml-0"> + <div class="tw-font-normal tw-ms-2 empty:tw-ms-0"> <ng-content select="[slot=end]"></ng-content> </div> </a> diff --git a/libs/components/src/tabs/tabs.stories.ts b/libs/components/src/tabs/tabs.stories.ts index 250a7443065..5879dd2a14e 100644 --- a/libs/components/src/tabs/tabs.stories.ts +++ b/libs/components/src/tabs/tabs.stories.ts @@ -90,7 +90,7 @@ export const ContentTabs: Story = { <bit-tab label="Second Tab">Second Tab Content</bit-tab> <bit-tab> <ng-template bitTabLabel> - <i class="bwi bwi-search tw-pr-1"></i> Template Label + <i class="bwi bwi-search tw-pe-1"></i> Template Label </ng-template> Template Label Content </bit-tab> @@ -112,7 +112,7 @@ export const NavigationTabs: Story = { <bit-tab-link [route]="['item-3']">Item 3</bit-tab-link> <bit-tab-link [route]="['item-with-child-counter']"> Item With Counter - <div slot="end" class="tw-pl-2 tw-text-muted"> + <div slot="end" class="tw-ps-2 tw-text-muted"> 42 </div> </bit-tab-link> diff --git a/libs/components/src/toast/toast.component.html b/libs/components/src/toast/toast.component.html index bdbc9674184..8ebf6778286 100644 --- a/libs/components/src/toast/toast.component.html +++ b/libs/components/src/toast/toast.component.html @@ -19,7 +19,7 @@ </div> <!-- Overriding hover and focus-visible colors for a11y against colored background --> <button - class="tw-ml-auto hover:tw-border-text-main focus-visible:before:tw-ring-text-main" + class="tw-ms-auto hover:tw-border-text-main focus-visible:before:tw-ring-text-main" bitIconButton="bwi-close" buttonType="main" type="button" diff --git a/libs/components/src/toggle-group/toggle.component.ts b/libs/components/src/toggle-group/toggle.component.ts index bb48b7e103e..a9e32beb4af 100644 --- a/libs/components/src/toggle-group/toggle.component.ts +++ b/libs/components/src/toggle-group/toggle.component.ts @@ -72,8 +72,8 @@ export class ToggleComponent<TValue> implements AfterContentChecked, AfterViewIn "hover:tw-bg-primary-100", "group-first-of-type/toggle:tw-border-l", - "group-first-of-type/toggle:tw-rounded-l-full", - "group-last-of-type/toggle:tw-rounded-r-full", + "group-first-of-type/toggle:tw-rounded-s-full", + "group-last-of-type/toggle:tw-rounded-e-full", "peer-focus-visible/toggle-input:tw-outline-none", "peer-focus-visible/toggle-input:tw-ring", From 6bb484dc23eb621552ddcfdd905a2834e8767502 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Mon, 2 Jun 2025 09:06:03 -0700 Subject: [PATCH 210/245] [PM-22204] - update revision date from server response in shareManyWithServer (#15016) * update revision date from server response in shareManyWithServer * return CipherResponse instead of Record --- libs/common/src/abstractions/api.service.ts | 2 +- libs/common/src/services/api.service.ts | 4 ++-- libs/common/src/vault/services/cipher.service.ts | 14 +++++++++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 49543e2f2ce..aba7454384d 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -208,7 +208,7 @@ export abstract class ApiService { deleteManyCiphersAdmin: (request: CipherBulkDeleteRequest) => Promise<any>; putMoveCiphers: (request: CipherBulkMoveRequest) => Promise<any>; putShareCipher: (id: string, request: CipherShareRequest) => Promise<CipherResponse>; - putShareCiphers: (request: CipherBulkShareRequest) => Promise<any>; + putShareCiphers: (request: CipherBulkShareRequest) => Promise<CipherResponse[]>; putCipherCollections: ( id: string, request: CipherCollectionsRequest, diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 95aea41e68b..4d40f814a2b 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -532,8 +532,8 @@ export class ApiService implements ApiServiceAbstraction { return new CipherResponse(r); } - putShareCiphers(request: CipherBulkShareRequest): Promise<any> { - return this.send("PUT", "/ciphers/share", request, true, false); + async putShareCiphers(request: CipherBulkShareRequest): Promise<CipherResponse[]> { + return await this.send("PUT", "/ciphers/share", request, true, true); } async putCipherCollections( diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 0c948fe0c6b..13b94a2fed2 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -836,7 +836,7 @@ export class CipherService implements CipherServiceAbstraction { organizationId: string, collectionIds: string[], userId: UserId, - ): Promise<any> { + ) { const promises: Promise<any>[] = []; const encCiphers: Cipher[] = []; for (const cipher of ciphers) { @@ -851,7 +851,16 @@ export class CipherService implements CipherServiceAbstraction { await Promise.all(promises); const request = new CipherBulkShareRequest(encCiphers, collectionIds, userId); try { - await this.apiService.putShareCiphers(request); + const response = await this.apiService.putShareCiphers(request); + const responseMap = new Map(response.map((c) => [c.id, c])); + + encCiphers.forEach((cipher) => { + const matchingCipher = responseMap.get(cipher.id); + if (matchingCipher) { + cipher.revisionDate = new Date(matchingCipher.revisionDate); + } + }); + await this.upsert(encCiphers.map((c) => c.toCipherData())); } catch (e) { for (const cipher of ciphers) { cipher.organizationId = null; @@ -859,7 +868,6 @@ export class CipherService implements CipherServiceAbstraction { } throw e; } - await this.upsert(encCiphers.map((c) => c.toCipherData())); } saveAttachmentWithServer( From 2fbc4c1578b92ae100468aa97a02771bbf1235c1 Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Mon, 2 Jun 2025 19:13:31 +0200 Subject: [PATCH 211/245] [CL-525] Upgrade angular to v19 (#14815) Upgrade Angular to v19 using the update guide. - Add `standalone: false` to any missed component in stories or tests. - Update jest.config to follow the new best practices. --- apps/browser/jest.config.js | 1 - .../vault-popup-items.service.spec.ts | 19 +- apps/browser/tsconfig.spec.json | 6 +- apps/desktop/jest.config.js | 1 - apps/desktop/tsconfig.spec.json | 4 + apps/web/jest.config.js | 1 - .../organization-warnings.service.spec.ts | 4 +- .../app/layouts/header/web-header.stories.ts | 1 + apps/web/src/app/oss.module.ts | 3 + apps/web/src/app/shared/shared.module.ts | 3 - apps/web/tsconfig.spec.json | 4 + bitwarden_license/bit-web/jest.config.js | 1 - bitwarden_license/bit-web/tsconfig.spec.json | 4 + eslint.config.mjs | 1 + libs/admin-console/jest.config.js | 1 - libs/admin-console/tsconfig.spec.json | 4 + libs/angular/jest.config.js | 1 - .../src/auth/guards/active-auth.guard.spec.ts | 2 +- .../directives/if-feature.directive.spec.ts | 1 + .../platform/guard/feature-flag.guard.spec.ts | 2 +- libs/angular/tsconfig.spec.json | 4 + libs/auth/jest.config.js | 1 - .../two-factor-auth.component.spec.ts | 2 +- .../two-factor-auth.guard.spec.ts | 2 +- libs/auth/tsconfig.spec.json | 4 + libs/billing/jest.config.js | 1 - libs/billing/tsconfig.spec.json | 4 + libs/common/tsconfig.spec.json | 4 + libs/components/jest.config.js | 1 - .../src/button/button.component.spec.ts | 1 + .../src/dialog/dialog.service.stories.ts | 2 + ...ple-configurable-dialog.service.stories.ts | 1 + .../simple-dialog.service.stories.ts | 2 + .../form-field/password-input-toggle.spec.ts | 1 + .../src/menu/menu.component.spec.ts | 1 + .../radio-button.component.spec.ts | 5 +- .../radio-group.component.spec.ts | 1 + .../toggle-group.component.spec.ts | 3 +- .../src/toggle-group/toggle.component.spec.ts | 3 +- libs/components/tsconfig.json | 35 +- libs/components/tsconfig.spec.json | 4 + libs/dirt/card/jest.config.js | 6 +- libs/dirt/card/tsconfig.spec.json | 4 + libs/eslint/jest.config.js | 1 - libs/importer/jest.config.js | 1 - libs/key-management-ui/jest.config.js | 1 - libs/key-management-ui/tsconfig.spec.json | 4 + libs/key-management/jest.config.js | 1 - libs/key-management/tsconfig.spec.json | 4 + libs/node/tsconfig.spec.json | 4 + libs/platform/jest.config.js | 1 - libs/platform/tsconfig.spec.json | 4 + libs/shared/jest.config.angular.js | 32 +- libs/tools/send/send-ui/jest.config.js | 17 +- libs/tools/send/send-ui/tsconfig.spec.json | 4 + libs/ui/common/src/setup-jest.ts | 13 +- libs/vault/jest.config.js | 1 - libs/vault/tsconfig.spec.json | 4 + package-lock.json | 6225 +++++++---------- package.json | 42 +- scripts/test-types.js | 2 +- 61 files changed, 2827 insertions(+), 3690 deletions(-) diff --git a/apps/browser/jest.config.js b/apps/browser/jest.config.js index 73f5ada287a..0d1034f0eac 100644 --- a/apps/browser/jest.config.js +++ b/apps/browser/jest.config.js @@ -7,7 +7,6 @@ const sharedConfig = require("../../libs/shared/jest.config.angular"); /** @type {import('jest').Config} */ module.exports = { ...sharedConfig, - preset: "jest-preset-angular", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( { "@bitwarden/common/spec": ["../../libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts index 52cb393c684..a573f99d3c1 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts @@ -1,5 +1,5 @@ import { WritableSignal, signal } from "@angular/core"; -import { TestBed, discardPeriodicTasks, fakeAsync, tick } from "@angular/core/testing"; +import { TestBed } from "@angular/core/testing"; import { mock } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom, timeout } from "rxjs"; @@ -483,22 +483,15 @@ describe("VaultPopupItemsService", () => { }); }); - it("should update searchText$ when applyFilter is called", fakeAsync(() => { - let latestValue: string | null; + it("should update searchText$ when applyFilter is called", (done) => { service.searchText$.subscribe((val) => { - latestValue = val; + expect(val).toEqual("test search"); + expect(viewCacheService.mockSignal()).toEqual("test search"); + done(); }); - tick(); - expect(latestValue!).toEqual(""); service.applyFilter("test search"); - tick(); - expect(latestValue!).toEqual("test search"); - - expect(viewCacheService.mockSignal()).toEqual("test search"); - - discardPeriodicTasks(); - })); + }); }); // A function to generate a list of ciphers of different types diff --git a/apps/browser/tsconfig.spec.json b/apps/browser/tsconfig.spec.json index 79b5f5bc4b6..eedff91d23b 100644 --- a/apps/browser/tsconfig.spec.json +++ b/apps/browser/tsconfig.spec.json @@ -1,7 +1,9 @@ { "extends": "./tsconfig.json", - "files": ["./test.setup.ts"], "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false, "esModuleInterop": true - } + }, + "files": ["./test.setup.ts"] } diff --git a/apps/desktop/jest.config.js b/apps/desktop/jest.config.js index 73f5ada287a..0d1034f0eac 100644 --- a/apps/desktop/jest.config.js +++ b/apps/desktop/jest.config.js @@ -7,7 +7,6 @@ const sharedConfig = require("../../libs/shared/jest.config.angular"); /** @type {import('jest').Config} */ module.exports = { ...sharedConfig, - preset: "jest-preset-angular", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( { "@bitwarden/common/spec": ["../../libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, diff --git a/apps/desktop/tsconfig.spec.json b/apps/desktop/tsconfig.spec.json index de184bd7608..d52d889aa78 100644 --- a/apps/desktop/tsconfig.spec.json +++ b/apps/desktop/tsconfig.spec.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "files": ["./test.setup.ts"] } diff --git a/apps/web/jest.config.js b/apps/web/jest.config.js index 9b5d6fdc766..724e44be009 100644 --- a/apps/web/jest.config.js +++ b/apps/web/jest.config.js @@ -7,7 +7,6 @@ const sharedConfig = require("../../libs/shared/jest.config.angular"); /** @type {import('jest').Config} */ module.exports = { ...sharedConfig, - preset: "jest-preset-angular", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: { // Replace ESM SDK with Node compatible SDK diff --git a/apps/web/src/app/billing/services/organization-warnings.service.spec.ts b/apps/web/src/app/billing/services/organization-warnings.service.spec.ts index 88c571e2d67..c75dde0c9e5 100644 --- a/apps/web/src/app/billing/services/organization-warnings.service.spec.ts +++ b/apps/web/src/app/billing/services/organization-warnings.service.spec.ts @@ -11,7 +11,9 @@ import { DialogService, SimpleDialogOptions } from "@bitwarden/components"; import { OrganizationWarningsService } from "./organization-warnings.service"; -describe("OrganizationWarningsService", () => { +// Skipped since Angular complains about `TypeError: Cannot read properties of undefined (reading 'ngModule')` +// which is typically a sign of circular dependencies. The problem seems to be originating from `ChangePlanDialogComponent`. +describe.skip("OrganizationWarningsService", () => { let dialogService: MockProxy<DialogService>; let i18nService: MockProxy<I18nService>; let organizationApiService: MockProxy<OrganizationApiServiceAbstraction>; diff --git a/apps/web/src/app/layouts/header/web-header.stories.ts b/apps/web/src/app/layouts/header/web-header.stories.ts index 571e78aab59..d3dc9604710 100644 --- a/apps/web/src/app/layouts/header/web-header.stories.ts +++ b/apps/web/src/app/layouts/header/web-header.stories.ts @@ -49,6 +49,7 @@ class MockStateService { @Component({ selector: "product-switcher", template: `<button type="button" bitIconButton="bwi-filter"></button>`, + standalone: false, }) class MockProductSwitcher {} diff --git a/apps/web/src/app/oss.module.ts b/apps/web/src/app/oss.module.ts index 39d0a9ae202..d5fe718412a 100644 --- a/apps/web/src/app/oss.module.ts +++ b/apps/web/src/app/oss.module.ts @@ -8,6 +8,9 @@ import { AccessComponent } from "./tools/send/send-access/access.component"; import { OrganizationBadgeModule } from "./vault/individual-vault/organization-badge/organization-badge.module"; import { VaultFilterModule } from "./vault/individual-vault/vault-filter/vault-filter.module"; +// Register the locales for the application +import "./shared/locales"; + @NgModule({ imports: [ SharedModule, diff --git a/apps/web/src/app/shared/shared.module.ts b/apps/web/src/app/shared/shared.module.ts index 1ad17139db8..efb4bb98be6 100644 --- a/apps/web/src/app/shared/shared.module.ts +++ b/apps/web/src/app/shared/shared.module.ts @@ -32,9 +32,6 @@ import { TypographyModule, } from "@bitwarden/components"; -// Register the locales for the application -import "./locales"; - /** * This NgModule should contain the most basic shared directives, pipes, and components. They * should be widely used by other modules to be considered for adding to this module. If in doubt diff --git a/apps/web/tsconfig.spec.json b/apps/web/tsconfig.spec.json index de184bd7608..d52d889aa78 100644 --- a/apps/web/tsconfig.spec.json +++ b/apps/web/tsconfig.spec.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "files": ["./test.setup.ts"] } diff --git a/bitwarden_license/bit-web/jest.config.js b/bitwarden_license/bit-web/jest.config.js index 9c9c61b2402..9eefd99528a 100644 --- a/bitwarden_license/bit-web/jest.config.js +++ b/bitwarden_license/bit-web/jest.config.js @@ -7,7 +7,6 @@ const sharedConfig = require("../../libs/shared/jest.config.angular"); /** @type {import('jest').Config} */ module.exports = { ...sharedConfig, - preset: "jest-preset-angular", setupFilesAfterEnv: ["../../apps/web/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( { diff --git a/bitwarden_license/bit-web/tsconfig.spec.json b/bitwarden_license/bit-web/tsconfig.spec.json index 6ac7373f389..6ae1ce91a43 100644 --- a/bitwarden_license/bit-web/tsconfig.spec.json +++ b/bitwarden_license/bit-web/tsconfig.spec.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "files": ["../../apps/web/test.setup.ts"] } diff --git a/eslint.config.mjs b/eslint.config.mjs index 5a4874457a0..8c607d9530c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -70,6 +70,7 @@ export default tseslint.config( "@angular-eslint/no-output-on-prefix": 0, "@angular-eslint/no-output-rename": 0, "@angular-eslint/no-outputs-metadata-property": 0, + "@angular-eslint/prefer-standalone": 0, "@angular-eslint/use-lifecycle-interface": "error", "@angular-eslint/use-pipe-transform-interface": 0, "@bitwarden/platform/required-using": "error", diff --git a/libs/admin-console/jest.config.js b/libs/admin-console/jest.config.js index 5131753964c..d59da5d68f5 100644 --- a/libs/admin-console/jest.config.js +++ b/libs/admin-console/jest.config.js @@ -8,7 +8,6 @@ const sharedConfig = require("../../libs/shared/jest.config.angular"); module.exports = { ...sharedConfig, displayName: "libs/admin-console tests", - preset: "jest-preset-angular", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( // lets us use @bitwarden/common/spec in tests diff --git a/libs/admin-console/tsconfig.spec.json b/libs/admin-console/tsconfig.spec.json index de184bd7608..d52d889aa78 100644 --- a/libs/admin-console/tsconfig.spec.json +++ b/libs/admin-console/tsconfig.spec.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "files": ["./test.setup.ts"] } diff --git a/libs/angular/jest.config.js b/libs/angular/jest.config.js index 5e73614eb8e..66a7e9a687a 100644 --- a/libs/angular/jest.config.js +++ b/libs/angular/jest.config.js @@ -8,7 +8,6 @@ const sharedConfig = require("../../libs/shared/jest.config.angular"); module.exports = { ...sharedConfig, displayName: "libs/angular tests", - preset: "jest-preset-angular", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( // lets us use @bitwarden/common/spec in tests diff --git a/libs/angular/src/auth/guards/active-auth.guard.spec.ts b/libs/angular/src/auth/guards/active-auth.guard.spec.ts index 566b2abc72a..de1bf40be11 100644 --- a/libs/angular/src/auth/guards/active-auth.guard.spec.ts +++ b/libs/angular/src/auth/guards/active-auth.guard.spec.ts @@ -13,7 +13,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { activeAuthGuard } from "./active-auth.guard"; -@Component({ template: "" }) +@Component({ template: "", standalone: false }) class EmptyComponent {} describe("activeAuthGuard", () => { diff --git a/libs/angular/src/directives/if-feature.directive.spec.ts b/libs/angular/src/directives/if-feature.directive.spec.ts index 456220b7911..d7c49994045 100644 --- a/libs/angular/src/directives/if-feature.directive.spec.ts +++ b/libs/angular/src/directives/if-feature.directive.spec.ts @@ -27,6 +27,7 @@ const testStringFeatureValue = "test-value"; </div> </div> `, + standalone: false, }) class TestComponent { testBooleanFeature = testBooleanFeature; diff --git a/libs/angular/src/platform/guard/feature-flag.guard.spec.ts b/libs/angular/src/platform/guard/feature-flag.guard.spec.ts index d39e071a693..3bc8b085a7d 100644 --- a/libs/angular/src/platform/guard/feature-flag.guard.spec.ts +++ b/libs/angular/src/platform/guard/feature-flag.guard.spec.ts @@ -12,7 +12,7 @@ import { I18nMockService, ToastService } from "@bitwarden/components/src"; import { canAccessFeature } from "./feature-flag.guard"; -@Component({ template: "" }) +@Component({ template: "", standalone: false }) export class EmptyComponent {} describe("canAccessFeature", () => { diff --git a/libs/angular/tsconfig.spec.json b/libs/angular/tsconfig.spec.json index de184bd7608..d52d889aa78 100644 --- a/libs/angular/tsconfig.spec.json +++ b/libs/angular/tsconfig.spec.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "files": ["./test.setup.ts"] } diff --git a/libs/auth/jest.config.js b/libs/auth/jest.config.js index 121d423be17..79b054f0741 100644 --- a/libs/auth/jest.config.js +++ b/libs/auth/jest.config.js @@ -8,7 +8,6 @@ const sharedConfig = require("../../libs/shared/jest.config.angular"); module.exports = { ...sharedConfig, displayName: "libs/auth tests", - preset: "jest-preset-angular", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( // lets us use @bitwarden/common/spec in tests diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts index a2769e37c87..76cbfe994a5 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts @@ -44,7 +44,7 @@ import { TwoFactorAuthComponentCacheService } from "./two-factor-auth-component- import { TwoFactorAuthComponentService } from "./two-factor-auth-component.service"; import { TwoFactorAuthComponent } from "./two-factor-auth.component"; -@Component({}) +@Component({ standalone: false }) class TestTwoFactorComponent extends TwoFactorAuthComponent {} describe("TwoFactorAuthComponent", () => { diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.spec.ts index 22cfe0820ef..06b998c5725 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.spec.ts @@ -11,7 +11,7 @@ import { LoginStrategyServiceAbstraction } from "../../common"; import { TwoFactorAuthGuard } from "./two-factor-auth.guard"; -@Component({ template: "" }) +@Component({ template: "", standalone: true }) export class EmptyComponent {} describe("TwoFactorAuthGuard", () => { diff --git a/libs/auth/tsconfig.spec.json b/libs/auth/tsconfig.spec.json index de184bd7608..d52d889aa78 100644 --- a/libs/auth/tsconfig.spec.json +++ b/libs/auth/tsconfig.spec.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "files": ["./test.setup.ts"] } diff --git a/libs/billing/jest.config.js b/libs/billing/jest.config.js index c43606191b9..5c24975e836 100644 --- a/libs/billing/jest.config.js +++ b/libs/billing/jest.config.js @@ -8,7 +8,6 @@ const sharedConfig = require("../../libs/shared/jest.config.angular"); module.exports = { ...sharedConfig, displayName: "libs/billing tests", - preset: "jest-preset-angular", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { prefix: "<rootDir>/", diff --git a/libs/billing/tsconfig.spec.json b/libs/billing/tsconfig.spec.json index de184bd7608..d52d889aa78 100644 --- a/libs/billing/tsconfig.spec.json +++ b/libs/billing/tsconfig.spec.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "files": ["./test.setup.ts"] } diff --git a/libs/common/tsconfig.spec.json b/libs/common/tsconfig.spec.json index de184bd7608..d52d889aa78 100644 --- a/libs/common/tsconfig.spec.json +++ b/libs/common/tsconfig.spec.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "files": ["./test.setup.ts"] } diff --git a/libs/components/jest.config.js b/libs/components/jest.config.js index 2f4b1ed15d4..082d378dced 100644 --- a/libs/components/jest.config.js +++ b/libs/components/jest.config.js @@ -8,7 +8,6 @@ const sharedConfig = require("../../libs/shared/jest.config.angular"); module.exports = { ...sharedConfig, displayName: "libs/components tests", - preset: "jest-preset-angular", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { prefix: "<rootDir>/", diff --git a/libs/components/src/button/button.component.spec.ts b/libs/components/src/button/button.component.spec.ts index d63f611a5f8..b20e4148b67 100644 --- a/libs/components/src/button/button.component.spec.ts +++ b/libs/components/src/button/button.component.spec.ts @@ -85,6 +85,7 @@ describe("Button", () => { <button id="disabled" type="button" bitButton disabled>Button</button> `, + standalone: false, }) class TestApp { buttonType: string; diff --git a/libs/components/src/dialog/dialog.service.stories.ts b/libs/components/src/dialog/dialog.service.stories.ts index e7c5a17c308..5db6577091d 100644 --- a/libs/components/src/dialog/dialog.service.stories.ts +++ b/libs/components/src/dialog/dialog.service.stories.ts @@ -20,6 +20,7 @@ interface Animal { @Component({ template: `<button bitButton type="button" (click)="openDialog()">Open Dialog</button>`, + standalone: false, }) class StoryDialogComponent { constructor(public dialogService: DialogService) {} @@ -49,6 +50,7 @@ class StoryDialogComponent { </ng-container> </bit-dialog> `, + standalone: false, }) class StoryDialogContentComponent { constructor( diff --git a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts index 87d6eb9fbfc..d703b6a6738 100644 --- a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.service.stories.ts @@ -31,6 +31,7 @@ import { DialogModule } from "../../dialog.module"; </bit-callout> } `, + standalone: false, }) class StoryDialogComponent { protected dialogs: { title: string; dialogs: SimpleDialogOptions[] }[] = [ diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts b/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts index 680ebe9ed3b..e5695a7ac5c 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts @@ -18,6 +18,7 @@ interface Animal { @Component({ template: `<button type="button" bitButton (click)="openDialog()">Open Simple Dialog</button>`, + standalone: false, }) class StoryDialogComponent { constructor(public dialogService: DialogService) {} @@ -48,6 +49,7 @@ class StoryDialogComponent { </ng-container> </bit-simple-dialog> `, + standalone: false, }) class StoryDialogContentComponent { constructor( diff --git a/libs/components/src/form-field/password-input-toggle.spec.ts b/libs/components/src/form-field/password-input-toggle.spec.ts index a3956e930ad..78b2521d643 100644 --- a/libs/components/src/form-field/password-input-toggle.spec.ts +++ b/libs/components/src/form-field/password-input-toggle.spec.ts @@ -25,6 +25,7 @@ import { BitPasswordInputToggleDirective } from "./password-input-toggle.directi </bit-form-field> </form> `, + standalone: false, }) class TestFormFieldComponent {} diff --git a/libs/components/src/menu/menu.component.spec.ts b/libs/components/src/menu/menu.component.spec.ts index 81d2ea64079..79924075873 100644 --- a/libs/components/src/menu/menu.component.spec.ts +++ b/libs/components/src/menu/menu.component.spec.ts @@ -73,5 +73,6 @@ describe("Menu", () => { <a id="item2" bitMenuItem>Item 2</a> </bit-menu> `, + standalone: false, }) class TestApp {} diff --git a/libs/components/src/radio-button/radio-button.component.spec.ts b/libs/components/src/radio-button/radio-button.component.spec.ts index f8cdae00664..617eb8454b4 100644 --- a/libs/components/src/radio-button/radio-button.component.spec.ts +++ b/libs/components/src/radio-button/radio-button.component.spec.ts @@ -71,12 +71,13 @@ describe("RadioButton", () => { class MockedButtonGroupComponent implements Partial<RadioGroupComponent> { onInputChange = jest.fn(); - selected = null; + selected: unknown = null; } @Component({ selector: "test-app", - template: ` <bit-radio-button [value]="value"><bit-label>Element</bit-label></bit-radio-button>`, + template: `<bit-radio-button [value]="value"><bit-label>Element</bit-label></bit-radio-button>`, + standalone: false, }) class TestApp { value?: string; diff --git a/libs/components/src/radio-button/radio-group.component.spec.ts b/libs/components/src/radio-button/radio-group.component.spec.ts index d2b9e6cbf2e..7ca99aaca17 100644 --- a/libs/components/src/radio-button/radio-group.component.spec.ts +++ b/libs/components/src/radio-button/radio-group.component.spec.ts @@ -75,6 +75,7 @@ describe("RadioGroupComponent", () => { <bit-radio-button value="third">Third</bit-radio-button> </bit-radio-group> `, + standalone: false, }) class TestApp { selected?: string; diff --git a/libs/components/src/toggle-group/toggle-group.component.spec.ts b/libs/components/src/toggle-group/toggle-group.component.spec.ts index e418a7b410c..6da5c4258f3 100644 --- a/libs/components/src/toggle-group/toggle-group.component.spec.ts +++ b/libs/components/src/toggle-group/toggle-group.component.spec.ts @@ -10,7 +10,7 @@ import { ToggleComponent } from "./toggle.component"; describe("Button", () => { let fixture: ComponentFixture<TestApp>; let testAppComponent: TestApp; - let buttonElements: ToggleComponent[]; + let buttonElements: ToggleComponent<unknown>[]; let radioButtons: HTMLInputElement[]; beforeEach(waitForAsync(() => { @@ -67,6 +67,7 @@ describe("Button", () => { <bit-toggle value="third">Third</bit-toggle> </bit-toggle-group> `, + standalone: false, }) class TestApp { selected?: string; diff --git a/libs/components/src/toggle-group/toggle.component.spec.ts b/libs/components/src/toggle-group/toggle.component.spec.ts index fe91f94071d..a4377b2e7b3 100644 --- a/libs/components/src/toggle-group/toggle.component.spec.ts +++ b/libs/components/src/toggle-group/toggle.component.spec.ts @@ -63,12 +63,13 @@ describe("Button", () => { class MockedButtonGroupComponent implements Partial<ToggleGroupComponent> { onInputInteraction = jest.fn(); - selected = null; + selected: unknown = null; } @Component({ selector: "test-app", template: ` <bit-toggle [value]="value">Element</bit-toggle>`, + standalone: false, }) class TestApp { value?: string; diff --git a/libs/components/tsconfig.json b/libs/components/tsconfig.json index eceaf0f3816..8f2b5edcc90 100644 --- a/libs/components/tsconfig.json +++ b/libs/components/tsconfig.json @@ -1,39 +1,14 @@ { - "compileOnSave": false, + "extends": "../shared/tsconfig", "compilerOptions": { - "baseUrl": "./", - "outDir": "./dist/out-tsc", - "forceConsistentCasingInFileNames": true, - "strict": false, - "noImplicitOverride": true, - "noPropertyAccessFromIndexSignature": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "sourceMap": true, - "declaration": false, - "downlevelIteration": true, - "experimentalDecorators": true, - "moduleResolution": "node", - "importHelpers": true, - "target": "es2017", - "module": "es2020", - "lib": ["es2020", "dom"], "paths": { "@bitwarden/common/*": ["../common/src/*"], "@bitwarden/platform": ["../platform/src"], "@bitwarden/ui-common": ["../ui/common/src"], + "@bitwarden/auth/common": ["../auth/src/common"], + "@bitwarden/admin-console/common": ["../admin-console/src/common"], + "@bitwarden/key-management": ["../key-management/src"], "@bitwarden/ui-common/setup-jest": ["../ui/common/src/setup-jest"] - }, - "plugins": [ - { - "name": "typescript-strict-plugin" - } - ] - }, - "angularCompilerOptions": { - "enableI18nLegacyMessageIdFormat": false, - "strictInjectionParameters": true, - "strictInputAccessModifiers": true, - "strictTemplates": true + } } } diff --git a/libs/components/tsconfig.spec.json b/libs/components/tsconfig.spec.json index de184bd7608..d52d889aa78 100644 --- a/libs/components/tsconfig.spec.json +++ b/libs/components/tsconfig.spec.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "files": ["./test.setup.ts"] } diff --git a/libs/dirt/card/jest.config.js b/libs/dirt/card/jest.config.js index 952e9ce0e2e..68455588d66 100644 --- a/libs/dirt/card/jest.config.js +++ b/libs/dirt/card/jest.config.js @@ -2,10 +2,12 @@ const { pathsToModuleNameMapper } = require("ts-jest"); const { compilerOptions } = require("../../../shared/tsconfig.spec"); +const sharedConfig = require("../../shared/jest.config.angular"); + /** @type {import('jest').Config} */ module.exports = { - testMatch: ["**/+(*.)+(spec).+(ts)"], - preset: "jest-preset-angular", + ...sharedConfig, + displayName: "tools/card tests", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { prefix: "<rootDir>/../../", diff --git a/libs/dirt/card/tsconfig.spec.json b/libs/dirt/card/tsconfig.spec.json index 919530506de..238f1a9dca0 100644 --- a/libs/dirt/card/tsconfig.spec.json +++ b/libs/dirt/card/tsconfig.spec.json @@ -1,5 +1,9 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "include": ["src"], "files": ["./test.setup.ts"], "exclude": ["node_modules", "dist"] diff --git a/libs/eslint/jest.config.js b/libs/eslint/jest.config.js index 5acadb023e4..118e698bae5 100644 --- a/libs/eslint/jest.config.js +++ b/libs/eslint/jest.config.js @@ -6,6 +6,5 @@ module.exports = { testEnvironment: "./fix-jsdom.ts", testMatch: ["**/+(*.)+(spec).+(mjs)"], displayName: "libs/eslint tests", - preset: "jest-preset-angular", setupFilesAfterEnv: ["<rootDir>/test.setup.mjs"], }; diff --git a/libs/importer/jest.config.js b/libs/importer/jest.config.js index ee5ae302b99..8d782d913a8 100644 --- a/libs/importer/jest.config.js +++ b/libs/importer/jest.config.js @@ -7,7 +7,6 @@ const sharedConfig = require("../shared/jest.config.ts"); /** @type {import('jest').Config} */ module.exports = { ...sharedConfig, - preset: "jest-preset-angular", testEnvironment: "jsdom", moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { prefix: "<rootDir>/", diff --git a/libs/key-management-ui/jest.config.js b/libs/key-management-ui/jest.config.js index 9373a8d2aed..ceeee3b2445 100644 --- a/libs/key-management-ui/jest.config.js +++ b/libs/key-management-ui/jest.config.js @@ -8,7 +8,6 @@ const sharedConfig = require("../../libs/shared/jest.config.angular"); module.exports = { ...sharedConfig, displayName: "libs/key management ui tests", - preset: "jest-preset-angular", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( // lets us use @bitwarden/common/spec in tests diff --git a/libs/key-management-ui/tsconfig.spec.json b/libs/key-management-ui/tsconfig.spec.json index de184bd7608..d52d889aa78 100644 --- a/libs/key-management-ui/tsconfig.spec.json +++ b/libs/key-management-ui/tsconfig.spec.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "files": ["./test.setup.ts"] } diff --git a/libs/key-management/jest.config.js b/libs/key-management/jest.config.js index ad8023e906b..4da81b0bd13 100644 --- a/libs/key-management/jest.config.js +++ b/libs/key-management/jest.config.js @@ -8,7 +8,6 @@ const sharedConfig = require("../../libs/shared/jest.config.angular"); module.exports = { ...sharedConfig, displayName: "libs/key management tests", - preset: "jest-preset-angular", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( // lets us use @bitwarden/common/spec in tests diff --git a/libs/key-management/tsconfig.spec.json b/libs/key-management/tsconfig.spec.json index de184bd7608..d52d889aa78 100644 --- a/libs/key-management/tsconfig.spec.json +++ b/libs/key-management/tsconfig.spec.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "files": ["./test.setup.ts"] } diff --git a/libs/node/tsconfig.spec.json b/libs/node/tsconfig.spec.json index de184bd7608..d52d889aa78 100644 --- a/libs/node/tsconfig.spec.json +++ b/libs/node/tsconfig.spec.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "files": ["./test.setup.ts"] } diff --git a/libs/platform/jest.config.js b/libs/platform/jest.config.js index 063fb847d8f..7d190940909 100644 --- a/libs/platform/jest.config.js +++ b/libs/platform/jest.config.js @@ -8,7 +8,6 @@ const sharedConfig = require("../../libs/shared/jest.config.angular"); module.exports = { ...sharedConfig, displayName: "libs/platform tests", - preset: "jest-preset-angular", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { prefix: "<rootDir>/", diff --git a/libs/platform/tsconfig.spec.json b/libs/platform/tsconfig.spec.json index de184bd7608..d52d889aa78 100644 --- a/libs/platform/tsconfig.spec.json +++ b/libs/platform/tsconfig.spec.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "files": ["./test.setup.ts"] } diff --git a/libs/shared/jest.config.angular.js b/libs/shared/jest.config.angular.js index 311318e46a4..6a9b52395f6 100644 --- a/libs/shared/jest.config.angular.js +++ b/libs/shared/jest.config.angular.js @@ -1,9 +1,20 @@ /* eslint-env node */ /* eslint-disable @typescript-eslint/no-require-imports */ -const { defaultTransformerOptions } = require("jest-preset-angular/presets"); +const { createCjsPreset } = require("jest-preset-angular/presets"); + +const presetConfig = createCjsPreset({ + tsconfig: "<rootDir>/tsconfig.spec.json", + astTransformers: { + before: ["<rootDir>/../../libs/shared/es2020-transformer.ts"], + }, + diagnostics: { + ignoreCodes: ["TS151001"], + }, +}); /** @type {import('jest').Config} */ module.exports = { + ...presetConfig, testMatch: ["**/+(*.)+(spec).+(ts)"], testPathIgnorePatterns: [ @@ -13,23 +24,4 @@ module.exports = { // Improves on-demand performance, for watches prefer 25%, overridable by setting --maxWorkers maxWorkers: "50%", - - transform: { - "^.+\\.(ts|js|mjs|svg)$": [ - "jest-preset-angular", - { - ...defaultTransformerOptions, - // Jest does not use tsconfig.spec.json by default - tsconfig: "<rootDir>/tsconfig.spec.json", - // Further workaround for memory leak, recommended here: - // https://github.com/kulshekhar/ts-jest/issues/1967#issuecomment-697494014 - // Makes tests run faster and reduces size/rate of leak, but loses typechecking on test code - // See https://bitwarden.atlassian.net/browse/EC-497 for more info - isolatedModules: true, - astTransformers: { - before: ["<rootDir>/../../libs/shared/es2020-transformer.ts"], - }, - }, - ], - }, }; diff --git a/libs/tools/send/send-ui/jest.config.js b/libs/tools/send/send-ui/jest.config.js index 952e9ce0e2e..ed8dbe61163 100644 --- a/libs/tools/send/send-ui/jest.config.js +++ b/libs/tools/send/send-ui/jest.config.js @@ -2,10 +2,23 @@ const { pathsToModuleNameMapper } = require("ts-jest"); const { compilerOptions } = require("../../../shared/tsconfig.spec"); +const { createCjsPreset } = require("jest-preset-angular/presets"); + +// FIXME: Should use the shared config! +const presetConfig = createCjsPreset({ + tsconfig: "<rootDir>/tsconfig.spec.json", + astTransformers: { + before: ["<rootDir>/../../../shared/es2020-transformer.ts"], + }, + diagnostics: { + ignoreCodes: ["TS151001"], + }, +}); + /** @type {import('jest').Config} */ module.exports = { - testMatch: ["**/+(*.)+(spec).+(ts)"], - preset: "jest-preset-angular", + ...presetConfig, + displayName: "tools/send-ui tests", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { prefix: "<rootDir>/../../", diff --git a/libs/tools/send/send-ui/tsconfig.spec.json b/libs/tools/send/send-ui/tsconfig.spec.json index 919530506de..238f1a9dca0 100644 --- a/libs/tools/send/send-ui/tsconfig.spec.json +++ b/libs/tools/send/send-ui/tsconfig.spec.json @@ -1,5 +1,9 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "include": ["src"], "files": ["./test.setup.ts"], "exclude": ["node_modules", "dist"] diff --git a/libs/ui/common/src/setup-jest.ts b/libs/ui/common/src/setup-jest.ts index cada139500f..16669ac17fb 100644 --- a/libs/ui/common/src/setup-jest.ts +++ b/libs/ui/common/src/setup-jest.ts @@ -1,12 +1,3 @@ -import "jest-preset-angular/setup-jest"; -import { getTestBed } from "@angular/core/testing"; -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting, -} from "@angular/platform-browser-dynamic/testing"; +import { setupZoneTestEnv } from "jest-preset-angular/setup-env/zone"; -getTestBed().resetTestEnvironment(); -getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { - errorOnUnknownElements: true, - errorOnUnknownProperties: true, -}); +setupZoneTestEnv({ errorOnUnknownElements: true, errorOnUnknownProperties: true }); diff --git a/libs/vault/jest.config.js b/libs/vault/jest.config.js index 16db37527ac..c0a0103da73 100644 --- a/libs/vault/jest.config.js +++ b/libs/vault/jest.config.js @@ -8,7 +8,6 @@ const sharedConfig = require("../../libs/shared/jest.config.angular"); module.exports = { ...sharedConfig, displayName: "libs/vault tests", - preset: "jest-preset-angular", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( // lets us use @bitwarden/common/spec in tests diff --git a/libs/vault/tsconfig.spec.json b/libs/vault/tsconfig.spec.json index de184bd7608..d52d889aa78 100644 --- a/libs/vault/tsconfig.spec.json +++ b/libs/vault/tsconfig.spec.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "files": ["./test.setup.ts"] } diff --git a/package-lock.json b/package-lock.json index 0807ea79775..01b58a11e2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,15 +15,15 @@ "libs/**/*" ], "dependencies": { - "@angular/animations": "18.2.13", - "@angular/cdk": "18.2.14", - "@angular/common": "18.2.13", - "@angular/compiler": "18.2.13", - "@angular/core": "18.2.13", - "@angular/forms": "18.2.13", - "@angular/platform-browser": "18.2.13", - "@angular/platform-browser-dynamic": "18.2.13", - "@angular/router": "18.2.13", + "@angular/animations": "19.2.14", + "@angular/cdk": "19.2.18", + "@angular/common": "19.2.14", + "@angular/compiler": "19.2.14", + "@angular/core": "19.2.14", + "@angular/forms": "19.2.14", + "@angular/platform-browser": "19.2.14", + "@angular/platform-browser-dynamic": "19.2.14", + "@angular/router": "19.2.14", "@bitwarden/sdk-internal": "0.2.0-main.177", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", @@ -31,7 +31,7 @@ "@koa/router": "13.1.0", "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", - "@ng-select/ng-select": "13.9.1", + "@ng-select/ng-select": "14.9.0", "argon2": "0.41.1", "argon2-browser": "1.18.0", "big-integer": "1.6.52", @@ -69,14 +69,14 @@ "tabbable": "6.2.0", "tldts": "7.0.1", "utf-8-validate": "6.0.5", - "zone.js": "0.14.10", + "zone.js": "0.15.0", "zxcvbn": "4.4.2" }, "devDependencies": { - "@angular-devkit/build-angular": "18.2.19", - "@angular-eslint/schematics": "18.4.3", - "@angular/cli": "18.2.19", - "@angular/compiler-cli": "18.2.13", + "@angular-devkit/build-angular": "19.2.14", + "@angular-eslint/schematics": "19.6.0", + "@angular/cli": "19.2.14", + "@angular/compiler-cli": "19.2.14", "@babel/core": "7.24.9", "@babel/preset-env": "7.24.8", "@compodoc/compodoc": "1.1.26", @@ -84,7 +84,7 @@ "@electron/rebuild": "3.7.2", "@eslint/compat": "1.2.9", "@lit-labs/signals": "0.1.2", - "@ngtools/webpack": "18.2.19", + "@ngtools/webpack": "19.2.14", "@storybook/addon-a11y": "8.6.12", "@storybook/addon-actions": "8.6.12", "@storybook/addon-designs": "8.2.1", @@ -121,7 +121,7 @@ "@typescript-eslint/utils": "8.31.0", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.16.1", - "angular-eslint": "18.4.3", + "angular-eslint": "19.6.0", "autoprefixer": "10.4.21", "axe-playwright": "2.1.0", "babel-loader": "9.2.1", @@ -153,7 +153,7 @@ "jest-diff": "29.7.0", "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", - "jest-preset-angular": "14.1.1", + "jest-preset-angular": "14.5.5", "json5": "2.2.3", "lint-staged": "16.0.0", "mini-css-extract-plugin": "2.9.2", @@ -401,14 +401,13 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1902.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.13.tgz", - "integrity": "sha512-ZMj+PjK22Ph2U8usG6L7LqEfvWlbaOvmiWXSrEt9YiC9QJt6rsumCkOgUIsmHQtucm/lK+9CMtyYdwH2fYycjg==", + "version": "0.1902.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.14.tgz", + "integrity": "sha512-rgMkqOrxedzqLZ8w59T/0YrpWt7LDmGwt+ZhNHE7cn27jZ876yGC2Bhcn58YZh2+R03WEJ9q0ePblaBYz03SMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@angular-devkit/core": "19.2.13", + "@angular-devkit/core": "19.2.14", "rxjs": "7.8.1" }, "engines": { @@ -418,17 +417,17 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.19.tgz", - "integrity": "sha512-xwY7v+nGE7TXOc4pgY6u57bLzIPSHuecosYr3TiWHAl9iEcKHzkCCFKsLZyunohHmq/i1uA6g3cC6iwp2xNYyg==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-19.2.14.tgz", + "integrity": "sha512-0K8vZxXdkME31fd6/+WACug8j4eLlU7mxR2/XJvS+VQ+a7bqdEsVddZDkwdWE+Y3ccZXvD/aNLZSEuSKmVFsnA==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.19", - "@angular-devkit/build-webpack": "0.1802.19", - "@angular-devkit/core": "18.2.19", - "@angular/build": "18.2.19", + "@angular-devkit/architect": "0.1902.14", + "@angular-devkit/build-webpack": "0.1902.14", + "@angular-devkit/core": "19.2.14", + "@angular/build": "19.2.14", "@babel/core": "7.26.10", "@babel/generator": "7.26.10", "@babel/helper-annotate-as-pure": "7.25.9", @@ -438,50 +437,45 @@ "@babel/plugin-transform-runtime": "7.26.10", "@babel/preset-env": "7.26.9", "@babel/runtime": "7.26.10", - "@discoveryjs/json-ext": "0.6.1", - "@ngtools/webpack": "18.2.19", + "@discoveryjs/json-ext": "0.6.3", + "@ngtools/webpack": "19.2.14", + "@vitejs/plugin-basic-ssl": "1.2.0", "ansi-colors": "4.1.3", "autoprefixer": "10.4.20", - "babel-loader": "9.1.3", + "babel-loader": "9.2.1", "browserslist": "^4.21.5", "copy-webpack-plugin": "12.0.2", - "critters": "0.0.24", "css-loader": "7.1.2", - "esbuild-wasm": "0.23.0", - "fast-glob": "3.3.2", + "esbuild-wasm": "0.25.4", + "fast-glob": "3.3.3", "http-proxy-middleware": "3.0.5", - "https-proxy-agent": "7.0.5", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", "karma-source-map-support": "1.4.0", - "less": "4.2.0", + "less": "4.2.2", "less-loader": "12.2.0", "license-webpack-plugin": "4.0.2", "loader-utils": "3.3.1", - "magic-string": "0.30.11", - "mini-css-extract-plugin": "2.9.0", - "mrmime": "2.0.0", + "mini-css-extract-plugin": "2.9.2", "open": "10.1.0", "ora": "5.4.1", - "parse5-html-rewriting-stream": "7.0.0", "picomatch": "4.0.2", - "piscina": "4.6.1", - "postcss": "8.4.41", + "piscina": "4.8.0", + "postcss": "8.5.2", "postcss-loader": "8.1.1", "resolve-url-loader": "5.0.0", "rxjs": "7.8.1", - "sass": "1.77.6", - "sass-loader": "16.0.0", - "semver": "7.6.3", + "sass": "1.85.0", + "sass-loader": "16.0.5", + "semver": "7.7.1", "source-map-loader": "5.0.0", "source-map-support": "0.5.21", - "terser": "5.31.6", + "terser": "5.39.0", "tree-kill": "1.2.2", - "tslib": "2.6.3", - "watchpack": "2.4.1", - "webpack": "5.94.0", + "tslib": "2.8.1", + "webpack": "5.98.0", "webpack-dev-middleware": "7.4.2", - "webpack-dev-server": "5.0.4", + "webpack-dev-server": "5.2.0", "webpack-merge": "6.0.1", "webpack-subresource-integrity": "5.1.0" }, @@ -491,22 +485,23 @@ "yarn": ">= 1.13.0" }, "optionalDependencies": { - "esbuild": "0.23.0" + "esbuild": "0.25.4" }, "peerDependencies": { - "@angular/compiler-cli": "^18.0.0", - "@angular/localize": "^18.0.0", - "@angular/platform-server": "^18.0.0", - "@angular/service-worker": "^18.0.0", - "@web/test-runner": "^0.18.0", + "@angular/compiler-cli": "^19.0.0 || ^19.2.0-next.0", + "@angular/localize": "^19.0.0 || ^19.2.0-next.0", + "@angular/platform-server": "^19.0.0 || ^19.2.0-next.0", + "@angular/service-worker": "^19.0.0 || ^19.2.0-next.0", + "@angular/ssr": "^19.2.14", + "@web/test-runner": "^0.20.0", "browser-sync": "^3.0.2", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "karma": "^6.3.0", - "ng-packagr": "^18.0.0", + "ng-packagr": "^19.0.0 || ^19.2.0-next.0", "protractor": "^7.0.0", - "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=5.4 <5.6" + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "typescript": ">=5.5 <5.9" }, "peerDependenciesMeta": { "@angular/localize": { @@ -518,6 +513,9 @@ "@angular/service-worker": { "optional": true }, + "@angular/ssr": { + "optional": true + }, "@web/test-runner": { "optional": true }, @@ -544,50 +542,6 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { - "version": "0.1802.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz", - "integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "18.2.19", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", - "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, "node_modules/@angular-devkit/build-angular/node_modules/@babel/core": { "version": "7.26.10", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", @@ -629,22 +583,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/@babel/preset-env": { "version": "7.26.9", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz", @@ -739,56 +677,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@types/express": { - "version": "4.17.22", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz", - "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -827,70 +715,10 @@ "postcss": "^8.1.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/babel-loader": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", - "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-cache-dir": "^4.0.0", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, "node_modules/@angular-devkit/build-angular/node_modules/browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", "dev": true, "funding": [ { @@ -908,8 +736,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001716", - "electron-to-chromium": "^1.5.149", + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -920,57 +748,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/@angular-devkit/build-angular/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -978,23 +755,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@angular-devkit/build-angular/node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@angular-devkit/build-angular/node_modules/copy-webpack-plugin": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", @@ -1057,194 +817,6 @@ "node": ">=4.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular-devkit/build-angular/node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular-devkit/build-angular/node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular-devkit/build-angular/node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/is-wsl": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", @@ -1261,22 +833,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@angular-devkit/build-angular/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -1284,53 +840,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@angular-devkit/build-angular/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular-devkit/build-angular/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@angular-devkit/build-angular/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -1354,37 +863,6 @@ "node": ">= 0.6" } }, - "node_modules/@angular-devkit/build-angular/node_modules/mini-css-extract-plugin": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", - "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "schema-utils": "^4.0.0", - "tapable": "^2.2.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/open": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", @@ -1404,34 +882,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@angular-devkit/build-angular/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@angular-devkit/build-angular/node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", + "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==", "dev": true, "funding": [ { @@ -1449,97 +903,23 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, - "node_modules/@angular-devkit/build-angular/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/rimraf": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", - "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/sass": { - "version": "1.77.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", - "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.85.0.tgz", + "integrity": "sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==", "dev": true, "license": "MIT", "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", + "chokidar": "^4.0.0", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -1547,12 +927,15 @@ }, "engines": { "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, "node_modules/@angular-devkit/build-angular/node_modules/sass-loader": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.0.tgz", - "integrity": "sha512-n13Z+3rU9A177dk4888czcVFiC8CL9dii4qpXWUg3YIIgZEvi9TCFKjOQcbK0kJM7DJu9VucrZFddvNfYCPwtw==", + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.5.tgz", + "integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==", "dev": true, "license": "MIT", "dependencies": { @@ -1591,9 +974,9 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true, "license": "ISC", "bin": { @@ -1603,102 +986,20 @@ "node": ">=10" } }, - "node_modules/@angular-devkit/build-angular/node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular-devkit/build-angular/node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/webpack": { - "version": "5.94.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", - "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "version": "5.98.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", + "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", @@ -1710,9 +1011,9 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", + "schema-utils": "^4.3.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", + "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, @@ -1732,145 +1033,14 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-server": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz", - "integrity": "sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/bonjour": "^3.5.13", - "@types/connect-history-api-fallback": "^1.5.4", - "@types/express": "^4.17.21", - "@types/serve-index": "^1.9.4", - "@types/serve-static": "^1.15.5", - "@types/sockjs": "^0.3.36", - "@types/ws": "^8.5.10", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.2.1", - "chokidar": "^3.6.0", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.4.0", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.1.0", - "launch-editor": "^2.6.1", - "open": "^10.0.3", - "p-retry": "^6.2.0", - "rimraf": "^5.0.5", - "schema-utils": "^4.2.0", - "selfsigned": "^2.4.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^7.1.0", - "ws": "^8.16.0" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", - "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/webpack/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "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/@angular-devkit/build-angular/node_modules/webpack/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/webpack/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1802.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.19.tgz", - "integrity": "sha512-axz1Sasn+c+GJpJexBL+B3Rh1w3wJrQq8k8gkniodjJ594p4ti2qGk7i9Tj8A4cXx5fGY+EpuZvKfI/9Tr7QwA==", + "version": "0.1902.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1902.14.tgz", + "integrity": "sha512-XDNB8Nlau/v59Ukd6UgBRBRnTnUmC244832SECmMxXHs1ljJMWGlI1img2xPErGd8426rUA9Iws4RkQiqbsybQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1802.19", + "@angular-devkit/architect": "0.1902.14", "rxjs": "7.8.1" }, "engines": { @@ -1883,129 +1053,12 @@ "webpack-dev-server": "^5.0.2" } }, - "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { - "version": "0.1802.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz", - "integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "18.2.19", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", - "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/build-webpack/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/@angular-devkit/build-webpack/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@angular-devkit/build-webpack/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/@angular-devkit/build-webpack/node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@angular-devkit/core": { - "version": "19.2.13", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.13.tgz", - "integrity": "sha512-iq73hE5Uvms1w3uMUSk4i4NDXDMQ863VAifX8LOTadhG6U0xISjNJ11763egVCxQmaKmg7zbG4rda88wHJATzA==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.14.tgz", + "integrity": "sha512-aaPEnRNIBoYT4XrrYcZlHadX8vFDTUR+4wUgcmr0cNDLeWzWtoPFeVq8TQD6kFDeqovSx/UVEblGgg/28WvHyg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -2029,15 +1082,15 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.19.tgz", - "integrity": "sha512-P/0KjkzOf2ZShuShx3cBbjLI7XlcS6B/yCRBo1MQfCC4cZfmzPQoUEOSQeYZgy5pnC24f+dKh/+TWc5uYL/Lvg==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.14.tgz", + "integrity": "sha512-s89/MWXHy8+GP/cRfFbSECIG3FQQQwNVv44OOmghPVgKQgQ+EoE/zygL2hqKYTUPoPaS/IhNXdXjSE5pS9yLeg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.19", + "@angular-devkit/core": "19.2.14", "jsonc-parser": "3.3.1", - "magic-string": "0.30.11", + "magic-string": "0.30.17", "ora": "5.4.1", "rxjs": "7.8.1" }, @@ -2047,253 +1100,37 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", - "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/schematics/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@angular-eslint/builder": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.4.3.tgz", - "integrity": "sha512-NzmrXlr7GFE+cjwipY/CxBscZXNqnuK0us1mO6Z2T6MeH6m+rRcdlY/rZyKoRniyNNvuzl6vpEsfMIMmnfebrA==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.6.0.tgz", + "integrity": "sha512-hUdYS1mSB09b5ABi2tuWeMTVprYHW+x6KmeAFJfXC6aMOa4NYQBdetIjOLwr7qUDlq1S/+2+HiX/FO76FPHClw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": ">= 0.1800.0 < 0.1900.0", - "@angular-devkit/core": ">= 18.0.0 < 19.0.0" + "@angular-devkit/architect": ">= 0.1900.0 < 0.2000.0", + "@angular-devkit/core": ">= 19.0.0 < 20.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, - "node_modules/@angular-eslint/builder/node_modules/@angular-devkit/architect": { - "version": "0.1802.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz", - "integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "18.2.19", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-eslint/builder/node_modules/@angular-devkit/core": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", - "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-eslint/builder/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/@angular-eslint/builder/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@angular-eslint/builder/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/@angular-eslint/builder/node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.4.3.tgz", - "integrity": "sha512-zdrA8mR98X+U4YgHzUKmivRU+PxzwOL/j8G7eTOvBuq8GPzsP+hvak+tyxlgeGm9HsvpFj9ERHLtJ0xDUPs8fg==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.6.0.tgz", + "integrity": "sha512-ro+seaTAg5GvtJ72uWEEnP9J5mT0vtgdqH6YMrmMt4pZbSZxvkLfLjZGkXo/HjVDVcCjPnmZeMwKN+uoEc27Jg==", "dev": true, "license": "MIT" }, "node_modules/@angular-eslint/eslint-plugin": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.4.3.tgz", - "integrity": "sha512-AyJbupiwTBR81P6T59v+aULEnPpZBCBxL2S5QFWfAhNCwWhcof4GihvdK2Z87yhvzDGeAzUFSWl/beJfeFa+PA==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-19.6.0.tgz", + "integrity": "sha512-IOMfFi/rPNrPwxZwIGTqWw0C5pC2Facwg3llmJoQFq8w2sUE0nNBL5uSQv5dT8s6ucum4g+RFNYHNe20SEOvRw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.4.3", - "@angular-eslint/utils": "18.4.3" + "@angular-eslint/bundled-angular-compiler": "19.6.0", + "@angular-eslint/utils": "19.6.0" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", @@ -2302,14 +1139,14 @@ } }, "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.4.3.tgz", - "integrity": "sha512-ijGlX2N01ayMXTpeQivOA31AszO8OEbu9ZQUCxnu9AyMMhxyi2q50bujRChAvN9YXQfdQtbxuajxV6+aiWb5BQ==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-19.6.0.tgz", + "integrity": "sha512-SDGbNSCUuPmqVesy5SvRE2MV7AKvvA/bVJwL9Fz5KYCHYxJz1rrJ8FknjWAfmg0qO2TMs1ZI9hov8JL+Bc4BBw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.4.3", - "@angular-eslint/utils": "18.4.3", + "@angular-eslint/bundled-angular-compiler": "19.6.0", + "@angular-eslint/utils": "19.6.0", "aria-query": "5.3.2", "axobject-query": "4.1.0" }, @@ -2321,142 +1158,29 @@ } }, "node_modules/@angular-eslint/schematics": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.4.3.tgz", - "integrity": "sha512-D5maKn5e6n58+8n7jLFLD4g+RGPOPeDSsvPc1sqial5tEKLxAJQJS9WZ28oef3bhkob6C60D+1H0mMmEEVvyVA==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-19.6.0.tgz", + "integrity": "sha512-lJzwHju7bhJ3p+SZnY0JVwGjxF2q68gUdOYhdU62pglfYkS5lm+A5LM/VznRvdpZOH69vvZ9gizQ8W1P525cdw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": ">= 18.0.0 < 19.0.0", - "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0", - "@angular-eslint/eslint-plugin": "18.4.3", - "@angular-eslint/eslint-plugin-template": "18.4.3", - "ignore": "6.0.2", - "semver": "7.6.3", + "@angular-devkit/core": ">= 19.0.0 < 20.0.0", + "@angular-devkit/schematics": ">= 19.0.0 < 20.0.0", + "@angular-eslint/eslint-plugin": "19.6.0", + "@angular-eslint/eslint-plugin-template": "19.6.0", + "ignore": "7.0.4", + "semver": "7.7.2", "strip-json-comments": "3.1.1" } }, - "node_modules/@angular-eslint/schematics/node_modules/@angular-devkit/core": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", - "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-eslint/schematics/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/@angular-eslint/schematics/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@angular-eslint/schematics/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/@angular-eslint/schematics/node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@angular-eslint/schematics/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@angular-eslint/template-parser": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.4.3.tgz", - "integrity": "sha512-JZMPtEB8yNip3kg4WDEWQyObSo2Hwf+opq2ElYuwe85GQkGhfJSJ2CQYo4FSwd+c5MUQAqESNRg9QqGYauDsiw==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-19.6.0.tgz", + "integrity": "sha512-NGxXUZkI5lXjoKnmL51C8DoJx8AjwF9sonieC2EVxgXycK2MYAamFWYGHMiVemzFsg1nIv+JvhHITgjSjyC3HQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.4.3", + "@angular-eslint/bundled-angular-compiler": "19.6.0", "eslint-scope": "^8.0.2" }, "peerDependencies": { @@ -2465,13 +1189,13 @@ } }, "node_modules/@angular-eslint/utils": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.4.3.tgz", - "integrity": "sha512-w0bJ9+ELAEiPBSTPPm9bvDngfu1d8JbzUhvs2vU+z7sIz/HMwUZT5S4naypj2kNN0gZYGYrW0lt+HIbW87zTAQ==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-19.6.0.tgz", + "integrity": "sha512-ygtsmRKHNqrzG2mpUj1XwLNRoG+ikYkizsOuq5xPRM8o6dCw03H5eel4s7hnXT4c09WbpnoaVNi9O3xFLIETJQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.4.3" + "@angular-eslint/bundled-angular-compiler": "19.6.0" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", @@ -2480,9 +1204,9 @@ } }, "node_modules/@angular/animations": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.13.tgz", - "integrity": "sha512-rG5J5Ek5Hg+Tz2NjkNOaG6PupiNK/lPfophXpsR1t/nWujqnMWX2krahD/i6kgD+jNWNKCJCYSOVvCx/BHOtKA==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-19.2.14.tgz", + "integrity": "sha512-xhl8fLto5HHJdVj8Nb6EoBEiTAcXuWDYn1q5uHcGxyVH3kiwENWy/2OQXgCr2CuWo2e6hNUGzSLf/cjbsMNqEA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2491,56 +1215,65 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "18.2.13" + "@angular/common": "19.2.14", + "@angular/core": "19.2.14" } }, "node_modules/@angular/build": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.19.tgz", - "integrity": "sha512-dTqR+mhcZWtCRyOafvzHNVpYxMQnt8HHHqNM0kyEMzcztXL2L9zDlKr0H9d+AgGGq/v4qwCh+1gFDxsHByZwMQ==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-19.2.14.tgz", + "integrity": "sha512-PAUR8vZpGKXy0Vc5gpJkigOthoj5YeGDpeykl/yLi6sx6yAIlXcE0MD+LGehKeqFSBL56rEpn9n710lI7eTJwg==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.19", - "@babel/core": "7.25.2", - "@babel/helper-annotate-as-pure": "7.24.7", + "@angular-devkit/architect": "0.1902.14", + "@babel/core": "7.26.10", + "@babel/helper-annotate-as-pure": "7.25.9", "@babel/helper-split-export-declaration": "7.24.7", - "@babel/plugin-syntax-import-attributes": "7.24.7", - "@inquirer/confirm": "3.1.22", - "@vitejs/plugin-basic-ssl": "1.1.0", + "@babel/plugin-syntax-import-attributes": "7.26.0", + "@inquirer/confirm": "5.1.6", + "@vitejs/plugin-basic-ssl": "1.2.0", + "beasties": "0.3.2", "browserslist": "^4.23.0", - "critters": "0.0.24", - "esbuild": "0.23.0", - "fast-glob": "3.3.2", - "https-proxy-agent": "7.0.5", - "listr2": "8.2.4", - "lmdb": "3.0.13", - "magic-string": "0.30.11", - "mrmime": "2.0.0", + "esbuild": "0.25.4", + "fast-glob": "3.3.3", + "https-proxy-agent": "7.0.6", + "istanbul-lib-instrument": "6.0.3", + "listr2": "8.2.5", + "magic-string": "0.30.17", + "mrmime": "2.0.1", "parse5-html-rewriting-stream": "7.0.0", "picomatch": "4.0.2", - "piscina": "4.6.1", - "rollup": "4.22.4", - "sass": "1.77.6", - "semver": "7.6.3", - "vite": "~5.4.17", - "watchpack": "2.4.1" + "piscina": "4.8.0", + "rollup": "4.34.8", + "sass": "1.85.0", + "semver": "7.7.1", + "source-map-support": "0.5.21", + "vite": "6.2.7", + "watchpack": "2.4.2" }, "engines": { "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, + "optionalDependencies": { + "lmdb": "3.2.6" + }, "peerDependencies": { - "@angular/compiler-cli": "^18.0.0", - "@angular/localize": "^18.0.0", - "@angular/platform-server": "^18.0.0", - "@angular/service-worker": "^18.0.0", + "@angular/compiler": "^19.0.0 || ^19.2.0-next.0", + "@angular/compiler-cli": "^19.0.0 || ^19.2.0-next.0", + "@angular/localize": "^19.0.0 || ^19.2.0-next.0", + "@angular/platform-server": "^19.0.0 || ^19.2.0-next.0", + "@angular/service-worker": "^19.0.0 || ^19.2.0-next.0", + "@angular/ssr": "^19.2.14", + "karma": "^6.4.0", "less": "^4.2.0", + "ng-packagr": "^19.0.0 || ^19.2.0-next.0", "postcss": "^8.4.0", - "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=5.4 <5.6" + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "typescript": ">=5.5 <5.9" }, "peerDependenciesMeta": { "@angular/localize": { @@ -2552,9 +1285,18 @@ "@angular/service-worker": { "optional": true }, + "@angular/ssr": { + "optional": true + }, + "karma": { + "optional": true + }, "less": { "optional": true }, + "ng-packagr": { + "optional": true + }, "postcss": { "optional": true }, @@ -2563,67 +1305,23 @@ } } }, - "node_modules/@angular/build/node_modules/@angular-devkit/architect": { - "version": "0.1802.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz", - "integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "18.2.19", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular/build/node_modules/@angular-devkit/core": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", - "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, "node_modules/@angular/build/node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -2648,54 +1346,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@angular/build/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", - "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@angular/build/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/@angular/build/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/@angular/build/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2703,75 +1353,15 @@ "dev": true, "license": "MIT" }, - "node_modules/@angular/build/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@angular/build/node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@angular/build/node_modules/immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular/build/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/@angular/build/node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@angular/build/node_modules/sass": { - "version": "1.77.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", - "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.85.0.tgz", + "integrity": "sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==", "dev": true, "license": "MIT", "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", + "chokidar": "^4.0.0", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -2779,12 +1369,15 @@ }, "engines": { "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, "node_modules/@angular/build/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true, "license": "ISC", "bin": { @@ -2794,45 +1387,115 @@ "node": ">=10" } }, - "node_modules/@angular/cdk": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.14.tgz", - "integrity": "sha512-vDyOh1lwjfVk9OqoroZAP8pf3xxKUvyl+TVR8nJxL4c5fOfUFkD7l94HaanqKSRwJcI2xiztuu92IVoHn8T33Q==", + "node_modules/@angular/build/node_modules/vite": { + "version": "6.2.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.7.tgz", + "integrity": "sha512-qg3LkeuinTrZoJHHF94coSaTfIPyBYoywp+ys4qu20oSJFbKMYoIJo0FWJT9q6Vp49l6z9IsJRbHdcGtiKbGoQ==", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "parse5": "^7.1.2" + "fsevents": "~2.3.3" }, "peerDependencies": { - "@angular/common": "^18.0.0 || ^19.0.0", - "@angular/core": "^18.0.0 || ^19.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/@angular/cdk": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.18.tgz", + "integrity": "sha512-aGMHOYK/VV9PhxGTUDwiu/4ozoR/RKz8cimI+QjRxEBhzn4EPqjUDSganvlhmgS7cTN3+aqozdvF/GopMRJjLg==", + "license": "MIT", + "dependencies": { + "parse5": "^7.1.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^19.0.0 || ^20.0.0", + "@angular/core": "^19.0.0 || ^20.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/cli": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.19.tgz", - "integrity": "sha512-LGVMTc36JQuw8QX8Sclxyei306EQW3KslopXbf7cfqt6D5/fHS+FqqA0O7V8ob/vOGMca+l6hQD27nW5Y3W6pA==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.2.14.tgz", + "integrity": "sha512-jZvNHAwmyhgUqSIs6OW8YH1rX9XKytm4zPxJol1Xk56F8yAhnrUtukcOi3b7Dv19Z+9eXkwV/Db+2dGjWIE0DA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1802.19", - "@angular-devkit/core": "18.2.19", - "@angular-devkit/schematics": "18.2.19", - "@inquirer/prompts": "5.3.8", - "@listr2/prompt-adapter-inquirer": "2.0.15", - "@schematics/angular": "18.2.19", + "@angular-devkit/architect": "0.1902.14", + "@angular-devkit/core": "19.2.14", + "@angular-devkit/schematics": "19.2.14", + "@inquirer/prompts": "7.3.2", + "@listr2/prompt-adapter-inquirer": "2.0.18", + "@schematics/angular": "19.2.14", "@yarnpkg/lockfile": "1.1.0", - "ini": "4.1.3", + "ini": "5.0.0", "jsonc-parser": "3.3.1", - "listr2": "8.2.4", - "npm-package-arg": "11.0.3", - "npm-pick-manifest": "9.1.0", - "pacote": "18.0.6", - "resolve": "1.22.8", - "semver": "7.6.3", + "listr2": "8.2.5", + "npm-package-arg": "12.0.2", + "npm-pick-manifest": "10.0.0", + "pacote": "20.0.0", + "resolve": "1.22.10", + "semver": "7.7.1", "symbol-observable": "4.0.0", "yargs": "17.7.2" }, @@ -2845,126 +1508,10 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { - "version": "0.1802.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz", - "integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "18.2.19", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", - "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular/cli/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/@angular/cli/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@angular/cli/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/@angular/cli/node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@angular/cli/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true, "license": "ISC", "bin": { @@ -2975,9 +1522,9 @@ } }, "node_modules/@angular/common": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.13.tgz", - "integrity": "sha512-4ZqrNp1PoZo7VNvW+sbSc2CB2axP1sCH2wXl8B0wdjsj8JY1hF1OhuugwhpAHtGxqewed2kCXayE+ZJqSTV4jw==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.2.14.tgz", + "integrity": "sha512-NcNklcuyqaTjOVGf7aru8APX9mjsnZ01gFZrn47BxHozhaR0EMRrotYQTdi8YdVjPkeYFYanVntSLfhyobq/jg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2986,38 +1533,30 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/core": "18.2.13", + "@angular/core": "19.2.14", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.13.tgz", - "integrity": "sha512-TzWcrkopyjFF+WeDr2cRe8CcHjU72KfYV3Sm2TkBkcXrkYX5sDjGWrBGrG3hRB4e4okqchrOCvm1MiTdy2vKMA==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-19.2.14.tgz", + "integrity": "sha512-ZqJDYOdhgKpVGNq3+n/Gbxma8DVYElDsoRe0tvNtjkWBVdaOxdZZUqmJ3kdCBsqD/aqTRvRBu0KGo9s2fCChkA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "engines": { "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/core": "18.2.13" - }, - "peerDependenciesMeta": { - "@angular/core": { - "optional": true - } } }, "node_modules/@angular/compiler-cli": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.13.tgz", - "integrity": "sha512-DBSh4AQwkiJDSiVvJATRmjxf6wyUs9pwQLgaFdSlfuTRO+sdb0J2z1r3BYm8t0IqdoyXzdZq2YCH43EmyvD71g==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-19.2.14.tgz", + "integrity": "sha512-e9/h86ETjoIK2yTLE9aUeMCKujdg/du2pq7run/aINjop4RtnNOw+ZlSTUa6R65lP5CVwDup1kPytpAoifw8cA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "7.25.2", + "@babel/core": "7.26.9", "@jridgewell/sourcemap-codec": "^1.4.14", "chokidar": "^4.0.0", "convert-source-map": "^1.5.1", @@ -3035,27 +1574,27 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/compiler": "18.2.13", - "typescript": ">=5.4 <5.6" + "@angular/compiler": "19.2.14", + "typescript": ">=5.5 <5.9" } }, "node_modules/@angular/compiler-cli/node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz", + "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.9", + "@babel/types": "^7.26.9", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -3088,9 +1627,9 @@ } }, "node_modules/@angular/core": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.13.tgz", - "integrity": "sha512-8mbWHMgO95OuFV1Ejy4oKmbe9NOJ3WazQf/f7wks8Bck7pcihd0IKhlPBNjFllbF5o+04EYSwFhEtvEgjMDClA==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.2.14.tgz", + "integrity": "sha512-EVErpW9tGqJ/wNcAN3G/ErH8pHCJ8mM1E6bsJ8UJIpDTZkpqqYjBMtZS9YWH5n3KwUd1tAkAB2w8FK125AjDUQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -3100,13 +1639,13 @@ }, "peerDependencies": { "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.14.10" + "zone.js": "~0.15.0" } }, "node_modules/@angular/forms": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.13.tgz", - "integrity": "sha512-A67D867fu3DSBhdLWWZl/F5pr7v2+dRM2u3U7ZJ0ewh4a+sv+0yqWdJW+a8xIoiHxS+btGEJL2qAKJiH+MCFfg==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-19.2.14.tgz", + "integrity": "sha512-hWtDOj2B0AuRTf+nkMJeodnFpDpmEK9OIhIv1YxcRe73ooaxrIdjgugkElO8I9Tj0E4/7m117ezhWDUkbqm1zA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -3115,16 +1654,16 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.13", - "@angular/core": "18.2.13", - "@angular/platform-browser": "18.2.13", + "@angular/common": "19.2.14", + "@angular/core": "19.2.14", + "@angular/platform-browser": "19.2.14", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/platform-browser": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.13.tgz", - "integrity": "sha512-tu7ZzY6qD3ATdWFzcTcsAKe7M6cJeWbT/4/bF9unyGO3XBPcNYDKoiz10+7ap2PUd0fmPwvuvTvSNJiFEBnB8Q==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.2.14.tgz", + "integrity": "sha512-hzkT5nmA64oVBQl6PRjdL4dIFT1n7lfM9rm5cAoS+6LUUKRgiE2d421Kpn/Hz3jaCJfo+calMIdtSMIfUJBmww==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -3133,9 +1672,9 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/animations": "18.2.13", - "@angular/common": "18.2.13", - "@angular/core": "18.2.13" + "@angular/animations": "19.2.14", + "@angular/common": "19.2.14", + "@angular/core": "19.2.14" }, "peerDependenciesMeta": { "@angular/animations": { @@ -3144,9 +1683,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.2.13.tgz", - "integrity": "sha512-kbQCf9+8EpuJC7buBxhSiwBtXvjAwAKh6MznD6zd2pyCYqfY6gfRCZQRtK59IfgVtKmEONWI9grEyNIRoTmqJg==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-19.2.14.tgz", + "integrity": "sha512-Hfz0z1KDQmIdnFXVFCwCPykuIsHPkr1uW2aY396eARwZ6PK8i0Aadcm1ZOnpd3MR1bMyDrJo30VRS5kx89QWvA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -3155,16 +1694,16 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.13", - "@angular/compiler": "18.2.13", - "@angular/core": "18.2.13", - "@angular/platform-browser": "18.2.13" + "@angular/common": "19.2.14", + "@angular/compiler": "19.2.14", + "@angular/core": "19.2.14", + "@angular/platform-browser": "19.2.14" } }, "node_modules/@angular/router": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.2.13.tgz", - "integrity": "sha512-VKmfgi/r/CkyBq9nChQ/ptmfu0JT/8ONnLVJ5H+SkFLRYJcIRyHLKjRihMCyVm6xM5yktOdCaW73NTQrFz7+bg==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-19.2.14.tgz", + "integrity": "sha512-cBTWY9Jx7YhbmDYDb7Hqz4Q7UNIMlKTkdKToJd2pbhIXyoS+kHVQrySmyca+jgvYMjWnIjsAEa3dpje12D4mFw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -3173,9 +1712,9 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "18.2.13", - "@angular/core": "18.2.13", - "@angular/platform-browser": "18.2.13", + "@angular/common": "19.2.14", + "@angular/core": "19.2.14", + "@angular/platform-browser": "19.2.14", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -3213,9 +1752,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", - "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz", + "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==", "dev": true, "license": "MIT", "engines": { @@ -3318,9 +1857,9 @@ } }, "node_modules/@babel/helper-compilation-targets/node_modules/browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", "dev": true, "funding": [ { @@ -3338,8 +1877,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001716", - "electron-to-chromium": "^1.5.149", + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -3383,13 +1922,13 @@ } }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", - "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" @@ -3424,13 +1963,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", - "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" @@ -3491,15 +2030,15 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", - "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.27.3" }, "engines": { "node": ">=6.9.0" @@ -3550,13 +2089,13 @@ } }, "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", - "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" @@ -3651,26 +2190,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", - "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.3.tgz", + "integrity": "sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", - "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.3.tgz", + "integrity": "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.27.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -3874,13 +2413,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", - "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -4143,9 +2682,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.1.tgz", - "integrity": "sha512-QEcFlMl9nGTgh1rn2nIeU5bkfb9BAjaQcWbiP4LvKxUot52ABcTkpcyJ7f2Q2U2RuQ84BNLgts3jRme2dTx6Fw==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.3.tgz", + "integrity": "sha512-+F8CnfhuLhwUACIJMLWnjz6zvzYM2r0yeIHKlbgfw7ml8rOMJsXNXV/hyRcb3nb493gRs4WvYpQAndWj/qQmkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4214,13 +2753,13 @@ } }, "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", - "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" @@ -4244,9 +2783,9 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.1.tgz", - "integrity": "sha512-ttDCqhfvpE9emVkXbPD8vyxxh4TWYACVybGkDj+oReOGwnp066ITEivDlLwe0b1R0+evJ13IXQuLNB5w1fhC5Q==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.3.tgz", + "integrity": "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==", "dev": true, "license": "MIT", "dependencies": { @@ -4592,15 +3131,15 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.2.tgz", - "integrity": "sha512-AIUHD7xJ1mCrj3uPozvtngY3s0xpv7Nu7DoUSnzNY6Xam1Cy4rUznR//pvMHOhQ4AvbCexhbqXCtpxGHOGOO6g==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.3.tgz", + "integrity": "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.3", "@babel/plugin-transform-parameters": "^7.27.1" }, "engines": { @@ -4712,13 +3251,13 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", - "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" @@ -5130,16 +3669,16 @@ } }, "node_modules/@babel/traverse": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", - "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.3.tgz", + "integrity": "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.1", - "@babel/parser": "^7.27.1", - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -5148,13 +3687,13 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", - "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz", + "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.1", - "@babel/types": "^7.27.1", + "@babel/parser": "^7.27.3", + "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -5164,9 +3703,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", - "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", + "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -5515,22 +4054,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@compodoc/compodoc/node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@compodoc/compodoc/node_modules/@babel/plugin-transform-private-methods": { "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.7.tgz", @@ -5687,6 +4210,16 @@ "node": ">= 6" } }, + "node_modules/@compodoc/compodoc/node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/@compodoc/compodoc/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5879,9 +4412,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.3.tgz", - "integrity": "sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", "funding": [ { "type": "github", @@ -5897,14 +4430,14 @@ "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, "node_modules/@csstools/css-color-parser": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.9.tgz", - "integrity": "sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", "funding": [ { "type": "github", @@ -5918,20 +4451,20 @@ "license": "MIT", "dependencies": { "@csstools/color-helpers": "^5.0.2", - "@csstools/css-calc": "^2.1.3" + "@csstools/css-calc": "^2.1.4" }, "engines": { "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", - "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", "funding": [ { "type": "github", @@ -5947,13 +4480,13 @@ "node": ">=18" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.3" + "@csstools/css-tokenizer": "^3.0.4" } }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", - "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", "funding": [ { "type": "github", @@ -6022,9 +4555,9 @@ "license": "MIT" }, "node_modules/@discoveryjs/json-ext": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.1.tgz", - "integrity": "sha512-boghen8F0Q8D+0/Q1/1r6DUEieUJ8w2a1gIknExMSHBsJFOr2+0KUfHiVYBvucPwl3+RU5PFBK833FjFCh3BhA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", "dev": true, "license": "MIT", "engines": { @@ -6602,9 +5135,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", - "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", "cpu": [ "ppc64" ], @@ -6619,9 +5152,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", - "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", "cpu": [ "arm" ], @@ -6636,9 +5169,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", - "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", "cpu": [ "arm64" ], @@ -6653,9 +5186,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", - "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", "cpu": [ "x64" ], @@ -6670,9 +5203,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", - "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", "cpu": [ "arm64" ], @@ -6687,9 +5220,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", - "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", "cpu": [ "x64" ], @@ -6704,9 +5237,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", - "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", "cpu": [ "arm64" ], @@ -6721,9 +5254,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", - "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", "cpu": [ "x64" ], @@ -6738,9 +5271,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", - "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", "cpu": [ "arm" ], @@ -6755,9 +5288,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", - "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", "cpu": [ "arm64" ], @@ -6772,9 +5305,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", - "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", "cpu": [ "ia32" ], @@ -6789,9 +5322,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", - "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", "cpu": [ "loong64" ], @@ -6806,9 +5339,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", - "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", "cpu": [ "mips64el" ], @@ -6823,9 +5356,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", - "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", "cpu": [ "ppc64" ], @@ -6840,9 +5373,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", - "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", "cpu": [ "riscv64" ], @@ -6857,9 +5390,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", - "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", "cpu": [ "s390x" ], @@ -6874,9 +5407,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", - "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", "cpu": [ "x64" ], @@ -6890,10 +5423,27 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", - "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", "cpu": [ "x64" ], @@ -6908,9 +5458,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", - "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", "cpu": [ "arm64" ], @@ -6925,9 +5475,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", - "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", "cpu": [ "x64" ], @@ -6942,9 +5492,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", - "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", "cpu": [ "x64" ], @@ -6959,9 +5509,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", - "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", "cpu": [ "arm64" ], @@ -6976,9 +5526,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", - "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", "cpu": [ "ia32" ], @@ -6993,9 +5543,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", - "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", "cpu": [ "x64" ], @@ -7412,107 +5962,130 @@ } }, "node_modules/@inquirer/checkbox": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.5.0.tgz", - "integrity": "sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.8.tgz", + "integrity": "sha512-d/QAsnwuHX2OPolxvYcgSj7A9DO9H6gVOy2DvBTx+P2LH2iRTo/RSGV3iwCzW024nP9hw98KIuDmdyhZQj1UQg==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/figures": "^1.0.5", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.13", + "@inquirer/figures": "^1.0.12", + "@inquirer/type": "^3.0.7", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/confirm": { - "version": "3.1.22", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.22.tgz", - "integrity": "sha512-gsAKIOWBm2Q87CDfs9fEo7wJT3fwWIJfnDGMn9Qy74gBnNFOACDNfhUzovubbJjWnKLGBln7/NcSmZwj5DuEXg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.6.tgz", + "integrity": "sha512-6ZXYK3M1XmaVBZX6FCfChgtponnL0R6I7k8Nu+kaoNkT828FVZTcca1MqmWQipaW2oNREQl5AaPCUOOCVNdRMw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^9.0.10", - "@inquirer/type": "^1.5.2" + "@inquirer/core": "^10.1.7", + "@inquirer/type": "^3.0.4" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/core": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", - "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", + "version": "10.1.13", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.13.tgz", + "integrity": "sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/figures": "^1.0.6", - "@inquirer/type": "^2.0.0", - "@types/mute-stream": "^0.0.4", - "@types/node": "^22.5.5", - "@types/wrap-ansi": "^3.0.0", + "@inquirer/figures": "^1.0.12", + "@inquirer/type": "^3.0.7", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", - "mute-stream": "^1.0.0", + "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", - "strip-ansi": "^6.0.1", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" - } - }, - "node_modules/@inquirer/core/node_modules/@inquirer/type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", - "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", - "dev": true, - "license": "MIT", - "dependencies": { - "mute-stream": "^1.0.0" }, - "engines": { - "node": ">=18" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.2.0.tgz", - "integrity": "sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.13.tgz", + "integrity": "sha512-WbicD9SUQt/K8O5Vyk9iC2ojq5RHoCLK6itpp2fHsWe44VxxcA9z3GTWlvjSTGmMQpZr+lbVmrxdHcumJoLbMA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", "external-editor": "^3.1.0" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/expand": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-2.3.0.tgz", - "integrity": "sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.15.tgz", + "integrity": "sha512-4Y+pbr/U9Qcvf+N/goHzPEXiHH8680lM3Dr3Y9h9FFw4gHS+zVpbj8LfbKWIb/jayIB4aSO4pWiBTrBYWkvi5A==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/figures": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.11.tgz", - "integrity": "sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", + "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==", "dev": true, "license": "MIT", "engines": { @@ -7520,129 +6093,190 @@ } }, "node_modules/@inquirer/input": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-2.3.0.tgz", - "integrity": "sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.12.tgz", + "integrity": "sha512-xJ6PFZpDjC+tC1P8ImGprgcsrzQRsUh9aH3IZixm1lAZFK49UGHxM3ltFfuInN2kPYNfyoPRh+tU4ftsjPLKqQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3" + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/number": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-1.1.0.tgz", - "integrity": "sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.15.tgz", + "integrity": "sha512-xWg+iYfqdhRiM55MvqiTCleHzszpoigUpN5+t1OMcRkJrUrw7va3AzXaxvS+Ak7Gny0j2mFSTv2JJj8sMtbV2g==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3" + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/password": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-2.2.0.tgz", - "integrity": "sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.15.tgz", + "integrity": "sha512-75CT2p43DGEnfGTaqFpbDC2p2EEMrq0S+IRrf9iJvYreMy5mAWj087+mdKyLHapUEPLjN10mNvABpGbk8Wdraw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", "ansi-escapes": "^4.3.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/prompts": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-5.3.8.tgz", - "integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.3.2.tgz", + "integrity": "sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^2.4.7", - "@inquirer/confirm": "^3.1.22", - "@inquirer/editor": "^2.1.22", - "@inquirer/expand": "^2.1.22", - "@inquirer/input": "^2.2.9", - "@inquirer/number": "^1.0.10", - "@inquirer/password": "^2.1.22", - "@inquirer/rawlist": "^2.2.4", - "@inquirer/search": "^1.0.7", - "@inquirer/select": "^2.4.7" + "@inquirer/checkbox": "^4.1.2", + "@inquirer/confirm": "^5.1.6", + "@inquirer/editor": "^4.2.7", + "@inquirer/expand": "^4.0.9", + "@inquirer/input": "^4.1.6", + "@inquirer/number": "^3.0.9", + "@inquirer/password": "^4.0.9", + "@inquirer/rawlist": "^4.0.9", + "@inquirer/search": "^3.0.9", + "@inquirer/select": "^4.0.9" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/rawlist": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-2.3.0.tgz", - "integrity": "sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.3.tgz", + "integrity": "sha512-7XrV//6kwYumNDSsvJIPeAqa8+p7GJh7H5kRuxirct2cgOcSWwwNGoXDRgpNFbY/MG2vQ4ccIWCi8+IXXyFMZA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.13", + "@inquirer/type": "^3.0.7", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/search": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-1.1.0.tgz", - "integrity": "sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.15.tgz", + "integrity": "sha512-YBMwPxYBrADqyvP4nNItpwkBnGGglAvCLVW8u4pRmmvOsHUtCAUIMbUrLX5B3tFL1/WsLGdQ2HNzkqswMs5Uaw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/figures": "^1.0.5", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.13", + "@inquirer/figures": "^1.0.12", + "@inquirer/type": "^3.0.7", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/select": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-2.5.0.tgz", - "integrity": "sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.2.3.tgz", + "integrity": "sha512-OAGhXU0Cvh0PhLz9xTF/kx6g6x+sP+PcyTiLvCrewI99P3BBeexD+VbuwkNDvqGkk3y2h5ZiWLeRP7BFlhkUDg==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/figures": "^1.0.5", - "@inquirer/type": "^1.5.3", + "@inquirer/core": "^10.1.13", + "@inquirer/figures": "^1.0.12", + "@inquirer/type": "^3.0.7", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@inquirer/type": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", - "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", + "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", "dev": true, "license": "MIT", - "dependencies": { - "mute-stream": "^1.0.0" - }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@isaacs/cliui": { @@ -7748,6 +6382,19 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -8415,19 +7062,42 @@ "license": "MIT" }, "node_modules/@listr2/prompt-adapter-inquirer": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.15.tgz", - "integrity": "sha512-MZrGem/Ujjd4cPTLYDfCZK2iKKeiO/8OX13S6jqxldLs0Prf2aGqVlJ77nMBqMv7fzqgXEgjrNHLXcKR8l9lOg==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.18.tgz", + "integrity": "sha512-0hz44rAcrphyXcA8IS7EJ2SCoaBZD2u5goE8S/e+q/DL+dOGpqpcLidVOFeLG3VgML62SXmfRLAhWt0zL1oW4Q==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/type": "^1.5.1" + "@inquirer/type": "^1.5.5" }, "engines": { "node": ">=18.0.0" }, "peerDependencies": { - "@inquirer/prompts": ">= 3 < 6" + "@inquirer/prompts": ">= 3 < 8" + } + }, + "node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", + "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@listr2/prompt-adapter-inquirer/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/@lit-labs/react": { @@ -8464,9 +7134,9 @@ } }, "node_modules/@lmdb/lmdb-darwin-arm64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.0.13.tgz", - "integrity": "sha512-uiKPB0Fv6WEEOZjruu9a6wnW/8jrjzlZbxXscMB8kuCJ1k6kHpcBnuvaAWcqhbI7rqX5GKziwWEdD+wi2gNLfA==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.2.6.tgz", + "integrity": "sha512-yF/ih9EJJZc72psFQbwnn8mExIWfTnzWJg+N02hnpXtDPETYLmQswIMBn7+V88lfCaFrMozJsUvcEQIkEPU0Gg==", "cpu": [ "arm64" ], @@ -8478,9 +7148,9 @@ ] }, "node_modules/@lmdb/lmdb-darwin-x64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.0.13.tgz", - "integrity": "sha512-bEVIIfK5mSQoG1R19qA+fJOvCB+0wVGGnXHT3smchBVahYBdlPn2OsZZKzlHWfb1E+PhLBmYfqB5zQXFP7hJig==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.2.6.tgz", + "integrity": "sha512-5BbCumsFLbCi586Bb1lTWQFkekdQUw8/t8cy++Uq251cl3hbDIGEwD9HAwh8H6IS2F6QA9KdKmO136LmipRNkg==", "cpu": [ "x64" ], @@ -8492,9 +7162,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-arm": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.0.13.tgz", - "integrity": "sha512-Yml1KlMzOnXj/tnW7yX8U78iAzTk39aILYvCPbqeewAq1kSzl+w59k/fiVkTBfvDi/oW/5YRxL+Fq+Y1Fr1r2Q==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.2.6.tgz", + "integrity": "sha512-+6XgLpMb7HBoWxXj+bLbiiB4s0mRRcDPElnRS3LpWRzdYSe+gFk5MT/4RrVNqd2MESUDmb53NUXw1+BP69bjiQ==", "cpu": [ "arm" ], @@ -8506,9 +7176,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-arm64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.0.13.tgz", - "integrity": "sha512-afbVrsMgZ9dUTNUchFpj5VkmJRxvht/u335jUJ7o23YTbNbnpmXif3VKQGCtnjSh+CZaqm6N3CPG8KO3zwyZ1Q==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.2.6.tgz", + "integrity": "sha512-l5VmJamJ3nyMmeD1ANBQCQqy7do1ESaJQfKPSm2IG9/ADZryptTyCj8N6QaYgIWewqNUrcbdMkJajRQAt5Qjfg==", "cpu": [ "arm64" ], @@ -8520,9 +7190,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-x64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.0.13.tgz", - "integrity": "sha512-vOtxu0xC0SLdQ2WRXg8Qgd8T32ak4SPqk5zjItRszrJk2BdeXqfGxBJbP7o4aOvSPSmSSv46Lr1EP4HXU8v7Kg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.2.6.tgz", + "integrity": "sha512-nDYT8qN9si5+onHYYaI4DiauDMx24OAiuZAUsEqrDy+ja/3EbpXPX/VAkMV8AEaQhy3xc4dRC+KcYIvOFefJ4Q==", "cpu": [ "x64" ], @@ -8534,9 +7204,9 @@ ] }, "node_modules/@lmdb/lmdb-win32-x64": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.0.13.tgz", - "integrity": "sha512-UCrMJQY/gJnOl3XgbWRZZUvGGBuKy6i0YNSptgMzHBjs+QYDYR1Mt/RLTOPy4fzzves65O1EDmlL//OzEqoLlA==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.2.6.tgz", + "integrity": "sha512-XlqVtILonQnG+9fH2N3Aytria7P/1fwDgDhl29rde96uH2sLB8CHORIf2PfuLVzFQJ7Uqp8py9AYwr3ZUCFfWg==", "cpu": [ "x64" ], @@ -8680,9 +7350,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.0.tgz", - "integrity": "sha512-m//7RlINx1F3sz3KqwY1WWzVgTcYX52HYk4bJ1hkBXV3zccAEth+jRvG8DBRrdaQuRsPAJOx2MH3zaHNCKL7Zg==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.1.tgz", + "integrity": "sha512-KG1CZhZfWg+u8pxeM/mByJDScJSrjjxLc8fwQqbsS8xCjBmQfMNEBTotYdNanKekepnfRI85GtgQlctLFpcYPw==", "dev": true, "license": "MIT", "dependencies": { @@ -8849,6 +7519,311 @@ "url": "https://github.com/sponsors/Brooooooklyn" } }, + "node_modules/@napi-rs/nice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz", + "integrity": "sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.0.1", + "@napi-rs/nice-android-arm64": "1.0.1", + "@napi-rs/nice-darwin-arm64": "1.0.1", + "@napi-rs/nice-darwin-x64": "1.0.1", + "@napi-rs/nice-freebsd-x64": "1.0.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.0.1", + "@napi-rs/nice-linux-arm64-gnu": "1.0.1", + "@napi-rs/nice-linux-arm64-musl": "1.0.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.0.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.0.1", + "@napi-rs/nice-linux-s390x-gnu": "1.0.1", + "@napi-rs/nice-linux-x64-gnu": "1.0.1", + "@napi-rs/nice-linux-x64-musl": "1.0.1", + "@napi-rs/nice-win32-arm64-msvc": "1.0.1", + "@napi-rs/nice-win32-ia32-msvc": "1.0.1", + "@napi-rs/nice-win32-x64-msvc": "1.0.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz", + "integrity": "sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.0.1.tgz", + "integrity": "sha512-GqvXL0P8fZ+mQqG1g0o4AO9hJjQaeYG84FRfZaYjyJtZZZcMjXW5TwkL8Y8UApheJgyE13TQ4YNUssQaTgTyvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-91k3HEqUl2fsrz/sKkuEkscj6EAj3/eZNCLqzD2AA0TtVbkQi8nqxZCZDMkfklULmxLkMxuUdKe7RvG/T6s2AA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.0.1.tgz", + "integrity": "sha512-jXnMleYSIR/+TAN/p5u+NkCA7yidgswx5ftqzXdD5wgy/hNR92oerTXHc0jrlBisbd7DpzoaGY4cFD7Sm5GlgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.0.1.tgz", + "integrity": "sha512-j+iJ/ezONXRQsVIB/FJfwjeQXX7A2tf3gEXs4WUGFrJjpe/z2KB7sOv6zpkm08PofF36C9S7wTNuzHZ/Iiccfw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.0.1.tgz", + "integrity": "sha512-G8RgJ8FYXYkkSGQwywAUh84m946UTn6l03/vmEXBYNJxQJcD+I3B3k5jmjFG/OPiU8DfvxutOP8bi+F89MCV7Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.0.1.tgz", + "integrity": "sha512-IMDak59/W5JSab1oZvmNbrms3mHqcreaCeClUjwlwDr0m3BoR09ZiN8cKFBzuSlXgRdZ4PNqCYNeGQv7YMTjuA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.0.1.tgz", + "integrity": "sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.0.1.tgz", + "integrity": "sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.0.1.tgz", + "integrity": "sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.0.1.tgz", + "integrity": "sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.0.1.tgz", + "integrity": "sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.0.1.tgz", + "integrity": "sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.0.1.tgz", + "integrity": "sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.0.1.tgz", + "integrity": "sha512-t7eBAyPUrWL8su3gDxw9xxxqNwZzAqKo0Szv3IjVQd1GpXXVkb6vBBQUuxfIYaXMzZLwlxRQ7uzM2vdUE9ULGw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.0.1.tgz", + "integrity": "sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz", @@ -8862,9 +7837,9 @@ } }, "node_modules/@ng-select/ng-select": { - "version": "13.9.1", - "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-13.9.1.tgz", - "integrity": "sha512-+DzQkQp8coGWZREflJM/qx7BXipV6HEVpZCXoa6fJJRHJfmUMsxa5uV6kUVmClUE98Rkffk9CPHt6kZcj8PuqQ==", + "version": "14.9.0", + "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-14.9.0.tgz", + "integrity": "sha512-f/E3EaSVwdKmwvZL43nS961bGaXR90F0Gtb8vA+ub8Hfwqjr1NTI6X7+yu5iMkqfy5ZW5cJdoGvo+kv8zcAkjQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.1" @@ -8874,15 +7849,15 @@ "npm": ">= 8" }, "peerDependencies": { - "@angular/common": "^18.0.0", - "@angular/core": "^18.0.0", - "@angular/forms": "^18.0.0" + "@angular/common": "^19.0.0", + "@angular/core": "^19.0.0", + "@angular/forms": "^19.0.0" } }, "node_modules/@ngtools/webpack": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.19.tgz", - "integrity": "sha512-bExj5JrByKPibsqBbn5Pjn8lo91AUOTsyP2hgKpnOnmSr62rhWSiRwXltgz2MCiZRmuUznpt93WiOLixgYfYvQ==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-19.2.14.tgz", + "integrity": "sha512-PqrY+eeSUoF6JC6NCEQRPE/0Y2umSllD/fsDE6pnQrvGfztBpj0Jt1WMhgEI8BBcl4S7QW0LhPynkBmnCvTUmw==", "dev": true, "license": "MIT", "engines": { @@ -8891,8 +7866,8 @@ "yarn": ">= 1.13.0" }, "peerDependencies": { - "@angular/compiler-cli": "^18.0.0", - "typescript": ">=5.4 <5.6", + "@angular/compiler-cli": "^19.0.0 || ^19.2.0-next.0", + "typescript": ">=5.5 <5.9", "webpack": "^5.54.0" } }, @@ -8935,9 +7910,9 @@ } }, "node_modules/@npmcli/agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", - "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", "dev": true, "license": "ISC", "dependencies": { @@ -8948,7 +7923,7 @@ "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/agent/node_modules/agent-base": { @@ -9012,24 +7987,23 @@ } }, "node_modules/@npmcli/git": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", - "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-6.0.3.tgz", + "integrity": "sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/promise-spawn": "^7.0.0", - "ini": "^4.1.3", + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", "lru-cache": "^10.0.1", - "npm-pick-manifest": "^9.0.0", - "proc-log": "^4.0.0", - "promise-inflight": "^1.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "semver": "^7.3.5", - "which": "^4.0.0" + "which": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/git/node_modules/isexe": { @@ -9050,19 +8024,19 @@ "license": "ISC" }, "node_modules/@npmcli/git/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/git/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "license": "ISC", "dependencies": { @@ -9072,24 +8046,24 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/installed-package-contents": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", - "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz", + "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", "dev": true, "license": "ISC", "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" }, "bin": { "installed-package-contents": "bin/index.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/move-file": { @@ -9171,32 +8145,32 @@ } }, "node_modules/@npmcli/node-gyp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz", + "integrity": "sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/package-json": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", - "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-6.2.0.tgz", + "integrity": "sha512-rCNLSB/JzNvot0SEyXqWZ7tX2B5dD2a1br2Dp0vSYVo5jh8Z0EZ7lS9TsZ1UtziddB1UfNUaMCc538/HztnJGA==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.0", + "@npmcli/git": "^6.0.0", "glob": "^10.2.2", - "hosted-git-info": "^7.0.0", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "proc-log": "^4.0.0", - "semver": "^7.5.3" + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/package-json/node_modules/glob": { @@ -9221,16 +8195,16 @@ } }, "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^10.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/package-json/node_modules/jackspeak": { @@ -9274,26 +8248,26 @@ } }, "node_modules/@npmcli/package-json/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/promise-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", - "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.2.tgz", + "integrity": "sha512-/bNJhjc+o6qL+Dwz/bqfTQClkEO5nTQ1ZEcdCkAQjhkZMHIh22LPG7fNh1enJP1NKWDqYiiABnjFCY7E0zHYtQ==", "dev": true, "license": "ISC", "dependencies": { - "which": "^4.0.0" + "which": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/promise-spawn/node_modules/isexe": { @@ -9307,9 +8281,9 @@ } }, "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "license": "ISC", "dependencies": { @@ -9319,35 +8293,35 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/redact": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", - "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.2.tgz", + "integrity": "sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==", "dev": true, "license": "ISC", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/run-script": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", - "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-9.1.0.tgz", + "integrity": "sha512-aoNSbxtkePXUlbZB+anS1LqsJdctG5n3UVhfU47+CDdwMi6uNTBMF9gPcQRnqghQd2FGzcwwIFBruFMxjhBewg==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "node-gyp": "^10.0.0", - "proc-log": "^4.0.0", - "which": "^4.0.0" + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/run-script/node_modules/isexe": { @@ -9361,19 +8335,19 @@ } }, "node_modules/@npmcli/run-script/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/run-script/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "license": "ISC", "dependencies": { @@ -9383,7 +8357,7 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@nx/nx-darwin-arm64": { @@ -9939,9 +8913,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", - "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", + "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", "cpu": [ "arm" ], @@ -9953,9 +8927,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", - "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", + "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", "cpu": [ "arm64" ], @@ -9967,9 +8941,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", - "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", + "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==", "cpu": [ "arm64" ], @@ -9981,9 +8955,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", - "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", + "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", "cpu": [ "x64" ], @@ -9994,10 +8968,38 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", + "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", + "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", - "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", + "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", "cpu": [ "arm" ], @@ -10009,9 +9011,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", - "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", + "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", "cpu": [ "arm" ], @@ -10023,9 +9025,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", - "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", + "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", "cpu": [ "arm64" ], @@ -10037,9 +9039,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", - "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", + "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", "cpu": [ "arm64" ], @@ -10050,10 +9052,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", + "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", - "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", + "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", "cpu": [ "ppc64" ], @@ -10065,9 +9081,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", - "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", + "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", "cpu": [ "riscv64" ], @@ -10078,10 +9094,25 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", + "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", - "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", + "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", "cpu": [ "s390x" ], @@ -10093,9 +9124,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", - "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", + "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", "cpu": [ "x64" ], @@ -10107,9 +9138,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", - "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", + "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", "cpu": [ "x64" ], @@ -10121,9 +9152,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", - "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", + "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", "cpu": [ "arm64" ], @@ -10135,9 +9166,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", - "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", + "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", "cpu": [ "ia32" ], @@ -10149,9 +9180,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", - "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", + "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", "cpu": [ "x64" ], @@ -10170,14 +9201,14 @@ "license": "MIT" }, "node_modules/@schematics/angular": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.19.tgz", - "integrity": "sha512-s9aynH/fwB/LT94miVfsaL2C4Qd5BLgjMzWFx7iJ8Hyv7FjOBGYO6eGVovjCt2c6/abG+GQAk4EBOCfg3AUtCA==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.2.14.tgz", + "integrity": "sha512-p/jvMwth67g7tOrziTx+yWRagIPtjx21TF2uU2Pv5bqTY+JjRTczJs3yHPmVpzJN+ptmw47K4/NeLJmVUGuBgA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.19", - "@angular-devkit/schematics": "18.2.19", + "@angular-devkit/core": "19.2.14", + "@angular-devkit/schematics": "19.2.14", "jsonc-parser": "3.3.1" }, "engines": { @@ -10186,106 +9217,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", - "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@schematics/angular/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/@schematics/angular/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@schematics/angular/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/@schematics/angular/node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -10311,32 +9242,32 @@ "license": "BSD-3-Clause" }, "node_modules/@sigstore/bundle": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", - "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", + "integrity": "sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/protobuf-specs": "^0.4.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", - "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-2.0.0.tgz", + "integrity": "sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/protobuf-specs": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.3.tgz", - "integrity": "sha512-RpacQhBlwpBWd7KEJsRKcBQalbV28fvkxwTOJIqhIuDysMMaJW47V4OqW30iJB9uRpqOSxxEAQFdr8tTattReQ==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.4.2.tgz", + "integrity": "sha512-F2ye+n1INNhqT0MW+LfUEvTUPc/nS70vICJcxorKl7/gV9CO39+EDCw+qHNKEqvsDWk++yGVKCbzK1qLPvmC8g==", "dev": true, "license": "Apache-2.0", "engines": { @@ -10344,44 +9275,44 @@ } }, "node_modules/@sigstore/sign": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", - "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-3.1.0.tgz", + "integrity": "sha512-knzjmaOHOov1Ur7N/z4B1oPqZ0QX5geUfhrVaqVlu+hl0EAoL4o+l0MSULINcD5GCWe3Z0+YJO8ues6vFlW0Yw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "make-fetch-happen": "^13.0.1", - "proc-log": "^4.2.0", + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/sign/node_modules/@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/sign/node_modules/cacache": { - "version": "18.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", - "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/fs": "^3.1.0", + "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", @@ -10389,13 +9320,23 @@ "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/@sigstore/sign/node_modules/fs-minipass": { @@ -10456,27 +9397,26 @@ "license": "ISC" }, "node_modules/@sigstore/sign/node_modules/make-fetch-happen": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", - "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", + "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "proc-log": "^4.2.0", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "ssri": "^10.0.0" + "ssri": "^12.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/sign/node_modules/minipass-collect": { @@ -10493,31 +9433,63 @@ } }, "node_modules/@sigstore/sign/node_modules/minipass-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", - "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "minizlib": "^3.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" }, "optionalDependencies": { "encoding": "^0.1.13" } }, - "node_modules/@sigstore/sign/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "node_modules/@sigstore/sign/node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@sigstore/sign/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@sigstore/sign/node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@sigstore/sign/node_modules/path-scurry": { @@ -10538,81 +9510,109 @@ } }, "node_modules/@sigstore/sign/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/sign/node_modules/ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@sigstore/sign/node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "node_modules/@sigstore/sign/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", "dev": true, "license": "ISC", "dependencies": { - "unique-slug": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=18" + } + }, + "node_modules/@sigstore/sign/node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/sign/node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/@sigstore/tuf": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", - "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-3.1.1.tgz", + "integrity": "sha512-eFFvlcBIoGwVkkwmTi/vEQFSva3xs5Ot3WmBcjgjVdiaoelBLQaQ/ZBfhlG0MnG0cmTYScPpk7eDdGDWUcFUmg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2", - "tuf-js": "^2.2.1" + "@sigstore/protobuf-specs": "^0.4.1", + "tuf-js": "^3.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/verify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", - "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-2.1.1.tgz", + "integrity": "sha512-hVJD77oT67aowHxwT4+M6PGOp+E2LtLdTK3+FC0lBO9T7sYwItDMXZ7Z07IDCvR1M717a4axbIWckrW67KMP/w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.1.0", - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sinclair/typebox": { @@ -11872,9 +10872,9 @@ } }, "node_modules/@thednp/event-listener": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@thednp/event-listener/-/event-listener-2.0.8.tgz", - "integrity": "sha512-bZY04sWSn2YWAqcuY/fYy03ynARYHwn8xzYgdqqcHBXsBXhOc+bbWwHyLwW28XAA2NjzjMPZZAM3N5D09i+zEQ==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@thednp/event-listener/-/event-listener-2.0.10.tgz", + "integrity": "sha512-TH7YVKmoKg6GBLqZB+ETXObofcqJ/Tp5ycheolvYZMjLbMpzYf6MmOWTcBtx8+zrhWy8deV0hYkPvDFioDXdVQ==", "dev": true, "license": "MIT", "engines": { @@ -11883,13 +10883,13 @@ } }, "node_modules/@thednp/position-observer": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@thednp/position-observer/-/position-observer-1.0.7.tgz", - "integrity": "sha512-MkUAMMgqZPxy71hpcrKr9ZtedMk+oIFbFs5B8uKD857iuYKRJxgJtC1Itus14EEM4qMyeN0x47AUZJmZJQyXbQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@thednp/position-observer/-/position-observer-1.0.8.tgz", + "integrity": "sha512-NZ1cKuGBwWXZjpJvmipet8GyYnV+lUyOyiyzfuzO2Y5lqAPvep0P2QHkKMqe6V5+yEqwhRLhKoQO23z5PPgZ1w==", "dev": true, "license": "MIT", "dependencies": { - "@thednp/shorty": "^2.0.10" + "@thednp/shorty": "^2.0.11" }, "engines": { "node": ">=16", @@ -11897,9 +10897,9 @@ } }, "node_modules/@thednp/shorty": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@thednp/shorty/-/shorty-2.0.10.tgz", - "integrity": "sha512-H+hs1lw3Yc1NfwG0b7F7YmVjxQZ31NO2+6zx+I+9XabHxdwPKjvYJnkKKXr7bSItgm2AFrfOn5+3veB6W4iauw==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@thednp/shorty/-/shorty-2.0.11.tgz", + "integrity": "sha512-D+rLHt1l7c608yCuzXYJ75aDNWeMVbor+m1HO/XibhiWRbCpD8r6TUv3ayJI+feVfCnBNfrH+p6LSDn9l99uBA==", "dev": true, "license": "MIT", "engines": { @@ -11940,17 +10940,17 @@ } }, "node_modules/@tufjs/models": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", - "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-3.0.1.tgz", + "integrity": "sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==", "dev": true, "license": "MIT", "dependencies": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.4" + "minimatch": "^9.0.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@tybys/wasm-util": { @@ -12527,16 +11527,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mute-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", - "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/node": { "version": "22.15.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", @@ -12823,13 +11813,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/wrap-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", - "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -13241,9 +12224,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.32.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", - "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz", + "integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==", "dev": true, "license": "MIT", "engines": { @@ -13379,9 +12362,9 @@ } }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.2.tgz", - "integrity": "sha512-vxtBno4xvowwNmO/ASL0Y45TpHqmNkAaDtz4Jqb+clmcVSSl8XCG/PNFFkGsXXXS6AMjP+ja/TtNCFFa1QwLRg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.8.tgz", + "integrity": "sha512-rsRK8T7yxraNRDmpFLZCWqpea6OlXPNRRCjWMx24O1V86KFol7u2gj9zJCv6zB1oJjtnzWceuqdnCgOipFcJPA==", "cpu": [ "arm64" ], @@ -13393,9 +12376,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.2.tgz", - "integrity": "sha512-qhVa8ozu92C23Hsmv0BF4+5Dyyd5STT1FolV4whNgbY6mj3kA0qsrGPe35zNR3wAN7eFict3s4Rc2dDTPBTuFQ==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.8.tgz", + "integrity": "sha512-16yEMWa+Olqkk8Kl6Bu0ltT5OgEedkSAsxcz1B3yEctrDYp3EMBu/5PPAGhWVGnwhtf3hNe3y15gfYBAjOv5tQ==", "cpu": [ "x64" ], @@ -13407,9 +12390,9 @@ ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.2.tgz", - "integrity": "sha512-zKKdm2uMXqLFX6Ac7K5ElnnG5VIXbDlFWzg4WJ8CGUedJryM5A3cTgHuGMw1+P5ziV8CRhnSEgOnurTI4vpHpg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.8.tgz", + "integrity": "sha512-ST4uqF6FmdZQgv+Q73FU1uHzppeT4mhX3IIEmHlLObrv5Ep50olWRz0iQ4PWovadjHMTAmpuJAGaAuCZYb7UAQ==", "cpu": [ "x64" ], @@ -13421,9 +12404,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.2.tgz", - "integrity": "sha512-8N1z1TbPnHH+iDS/42GJ0bMPLiGK+cUqOhNbMKtWJ4oFGzqSJk/zoXFzcQkgtI63qMcUI7wW1tq2usZQSb2jxw==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.8.tgz", + "integrity": "sha512-Z/A/4Rm2VWku2g25C3tVb986fY6unx5jaaCFpx1pbAj0OKkyuJ5wcQLHvNbIcJ9qhiYwXfrkB7JNlxrAbg7YFg==", "cpu": [ "arm" ], @@ -13435,9 +12418,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.2.tgz", - "integrity": "sha512-tjYzI9LcAXR9MYd9rO45m1s0B/6bJNuZ6jeOxo1pq1K6OBuRMMmfyvJYval3s9FPPGmrldYA3mi4gWDlWuTFGA==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.8.tgz", + "integrity": "sha512-HN0p7o38qKmDo3bZUiQa6gP7Qhf0sKgJZtRfSHi6JL2Gi4NaUVF0EO1sQ1RHbeQ4VvfjUGMh3QE5dxEh06BgQQ==", "cpu": [ "arm" ], @@ -13449,9 +12432,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.2.tgz", - "integrity": "sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.8.tgz", + "integrity": "sha512-HsoVqDBt9G69AN0KWeDNJW+7i8KFlwxrbbnJffgTGpiZd6Jw+Q95sqkXp8y458KhKduKLmXfVZGnKBTNxAgPjw==", "cpu": [ "arm64" ], @@ -13463,9 +12446,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.2.tgz", - "integrity": "sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.8.tgz", + "integrity": "sha512-VfR2yTDUbUvn+e/Aw22CC9fQg9zdShHAfwWctNBdOk7w9CHWl2OtYlcMvjzMAns8QxoHQoqn3/CEnZ4Ts7hfrA==", "cpu": [ "arm64" ], @@ -13477,9 +12460,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.2.tgz", - "integrity": "sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.8.tgz", + "integrity": "sha512-xUauVQNz4uDgs4UJJiUAwMe3N0PA0wvtImh7V0IFu++UKZJhssXbKHBRR4ecUJpUHCX2bc4Wc8sGsB6P+7BANg==", "cpu": [ "ppc64" ], @@ -13491,9 +12474,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.2.tgz", - "integrity": "sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.8.tgz", + "integrity": "sha512-GqyIB+CuSHGhhc8ph5RrurtNetYJjb6SctSHafqmdGcRuGi6uyTMR8l18hMEhZFsXdFMc/MpInPLvmNV22xn+A==", "cpu": [ "riscv64" ], @@ -13505,9 +12488,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.2.tgz", - "integrity": "sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.8.tgz", + "integrity": "sha512-eEU3rWIFRv60xaAbtsgwHNWRZGD7cqkpCvNtio/f1TjEE3HfKLzPNB24fA9X/8ZXQrGldE65b7UKK3PmO4eWIQ==", "cpu": [ "riscv64" ], @@ -13519,9 +12502,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.2.tgz", - "integrity": "sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.8.tgz", + "integrity": "sha512-GVLI0f4I4TlLqEUoOFvTWedLsJEdvsD0+sxhdvQ5s+N+m2DSynTs8h9jxR0qQbKlpHWpc2Ortz3z48NHRT4l+w==", "cpu": [ "s390x" ], @@ -13533,9 +12516,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.2.tgz", - "integrity": "sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.8.tgz", + "integrity": "sha512-GX1pZ/4ncUreB0Rlp1l7bhKAZ8ZmvDIgXdeb5V2iK0eRRF332+6gRfR/r5LK88xfbtOpsmRHU6mQ4N8ZnwvGEA==", "cpu": [ "x64" ], @@ -13547,9 +12530,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.2.tgz", - "integrity": "sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.8.tgz", + "integrity": "sha512-n1N84MnsvDupzVuYqJGj+2pb9s8BI1A5RgXHvtVFHedGZVBCFjDpQVRlmsFMt6xZiKwDPaqsM16O/1isCUGt7w==", "cpu": [ "x64" ], @@ -13561,9 +12544,9 @@ ] }, "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.2.tgz", - "integrity": "sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.8.tgz", + "integrity": "sha512-x94WnaU5g+pCPDVedfnXzoG6lCOF2xFGebNwhtbJCWfceE94Zj8aysSxdxotlrZrxnz5D3ijtyFUYtpz04n39Q==", "cpu": [ "wasm32" ], @@ -13571,7 +12554,7 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.9" + "@napi-rs/wasm-runtime": "^0.2.10" }, "engines": { "node": ">=14.0.0" @@ -13591,9 +12574,9 @@ } }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.2.tgz", - "integrity": "sha512-gtYTh4/VREVSLA+gHrfbWxaMO/00y+34htY7XpioBTy56YN2eBjkPrY1ML1Zys89X3RJDKVaogzwxlM1qU7egg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.8.tgz", + "integrity": "sha512-vst2u8EJZ5L6jhJ6iLis3w9rg16aYqRxQuBAMYQRVrPMI43693hLP7DuqyOBRKgsQXy9/jgh204k0ViHkqQgdg==", "cpu": [ "arm64" ], @@ -13605,9 +12588,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.2.tgz", - "integrity": "sha512-Ywv20XHvHTDRQs12jd3MY8X5C8KLjDbg/jyaal/QLKx3fAShhJyD4blEANInsjxW3P7isHx1Blt56iUDDJO3jg==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.8.tgz", + "integrity": "sha512-yb3LZOLMFqnA+/ShlE1E5bpYPGDsA590VHHJPB+efnyowT776GJXBoh82em6O9WmYBUq57YblGTcMYAFBm72HA==", "cpu": [ "ia32" ], @@ -13619,9 +12602,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.2.tgz", - "integrity": "sha512-friS8NEQfHaDbkThxopGk+LuE5v3iY0StruifjQEt7SLbA46OnfgMO15sOTkbpJkol6RB+1l1TYPXh0sCddpvA==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.8.tgz", + "integrity": "sha512-hHKFx+opG5BA3/owMXon8ypwSotBGTdblG6oda/iOu9+OEYnk0cxD2uIcGyGT8jCK578kV+xMrNxqbn8Zjlpgw==", "cpu": [ "x64" ], @@ -13633,16 +12616,16 @@ ] }, "node_modules/@vitejs/plugin-basic-ssl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", - "integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.2.0.tgz", + "integrity": "sha512-mkQnxTkcldAzIsomk1UuLfAu9n+kpQ3JbHcpCp7d2Oo6ITtji8pHS3QToOWjhPFvNQSnhlkAjmGbhv2QvwO/7Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.6.0" + "node": ">=14.21.3" }, "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" } }, "node_modules/@vitest/expect": { @@ -14223,16 +13206,6 @@ "acorn-walk": "^8.0.2" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -14374,19 +13347,19 @@ } }, "node_modules/angular-eslint": { - "version": "18.4.3", - "resolved": "https://registry.npmjs.org/angular-eslint/-/angular-eslint-18.4.3.tgz", - "integrity": "sha512-0ZjLzzADGRLUhZC8ZpwSo6CE/m6QhQB/oljMJ0mEfP+lB1sy1v8PBKNsJboIcfEEgGW669Z/efVQ3df88yJLYg==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/angular-eslint/-/angular-eslint-19.6.0.tgz", + "integrity": "sha512-9qfP6rR6De5xe9WyviD9Vdpg2F3iHTlo7T1129ms0AQXrG9/U/upIQmNUN+Jz9CiJcHDUsniyd+EL8hjuNYnOg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": ">= 18.0.0 < 19.0.0", - "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0", - "@angular-eslint/builder": "18.4.3", - "@angular-eslint/eslint-plugin": "18.4.3", - "@angular-eslint/eslint-plugin-template": "18.4.3", - "@angular-eslint/schematics": "18.4.3", - "@angular-eslint/template-parser": "18.4.3", + "@angular-devkit/core": ">= 19.0.0 < 20.0.0", + "@angular-devkit/schematics": ">= 19.0.0 < 20.0.0", + "@angular-eslint/builder": "19.6.0", + "@angular-eslint/eslint-plugin": "19.6.0", + "@angular-eslint/eslint-plugin-template": "19.6.0", + "@angular-eslint/schematics": "19.6.0", + "@angular-eslint/template-parser": "19.6.0", "@typescript-eslint/types": "^8.0.0", "@typescript-eslint/utils": "^8.0.0" }, @@ -14396,106 +13369,6 @@ "typescript-eslint": "^8.0.0" } }, - "node_modules/angular-eslint/node_modules/@angular-devkit/core": { - "version": "18.2.19", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", - "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.2", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/angular-eslint/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/angular-eslint/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/angular-eslint/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/angular-eslint/node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -15216,9 +14089,9 @@ } }, "node_modules/autoprefixer/node_modules/browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", "dev": true, "funding": [ { @@ -15236,8 +14109,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001716", - "electron-to-chromium": "^1.5.149", + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -15652,6 +14525,26 @@ "dev": true, "license": "MIT" }, + "node_modules/beasties": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.3.2.tgz", + "integrity": "sha512-p4AF8uYzm9Fwu8m/hSVTCPXrRBPmB34hQpHsec2KOaR9CZmgoU8IOv4Cvwq4hgz2p4hLMNbsdNl5XeA6XbAQwA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "htmlparser2": "^10.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.49", + "postcss-media-query-parser": "^0.2.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/bent": { "version": "7.3.12", "resolved": "https://registry.npmjs.org/bent/-/bent-7.3.12.tgz", @@ -15824,15 +14717,15 @@ } }, "node_modules/bootstrap.native": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/bootstrap.native/-/bootstrap.native-5.1.2.tgz", - "integrity": "sha512-jkXzWs1EopckMT5FIc2CS9PsGloOfmHqyC4dHv3nVC5gpnOFoJPVDpUCKsoMta46SBh46g312BI3aWth0zkRDw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/bootstrap.native/-/bootstrap.native-5.1.4.tgz", + "integrity": "sha512-GGplCHRSAaFNVinbWU9/CJbhO0fP3fHZgshagd1obAkg+8cgcXg19XrOrsUUuVcZFfjenhCaw+3uV2z1EilWsg==", "dev": true, "license": "MIT", "dependencies": { - "@thednp/event-listener": "^2.0.8", - "@thednp/position-observer": "^1.0.7", - "@thednp/shorty": "^2.0.10" + "@thednp/event-listener": "^2.0.10", + "@thednp/position-observer": "^1.0.8", + "@thednp/shorty": "^2.0.11" }, "engines": { "node": ">=16", @@ -16522,9 +15415,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001718", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", - "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", + "version": "1.0.30001720", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz", + "integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==", "dev": true, "funding": [ { @@ -16695,6 +15588,26 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/cheerio/node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -17632,9 +16545,9 @@ } }, "node_modules/core-js-compat/node_modules/browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", "dev": true, "funding": [ { @@ -17652,8 +16565,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001716", - "electron-to-chromium": "^1.5.149", + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -17805,23 +16718,6 @@ "integrity": "sha512-vQOuWmBgsgG1ovGeDi8m6Zeu1JaqH/JncrxKmaqMbv/LunyOQdLiQhPHtOsNlbUI05TocR5nod/Mbs3HYtr6sQ==", "license": "MIT" }, - "node_modules/critters": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.24.tgz", - "integrity": "sha512-Oyqew0FGM0wYUSNqR0L6AteO5MpMoUU0rhKRieXeiKs+PmRTxiJMyaunYB2KF6fQ3dzChXKCpbFOEJx3OQ1v/Q==", - "deprecated": "Ownership of Critters has moved to the Nuxt team, who will be maintaining the project going forward. If you'd like to keep using Critters, please switch to the actively-maintained fork at https://github.com/danielroe/beasties", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "chalk": "^4.1.0", - "css-select": "^5.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.2", - "htmlparser2": "^8.0.2", - "postcss": "^8.4.23", - "postcss-media-query-parser": "^0.2.3" - } - }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -18247,19 +17143,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/default-require-extensions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", @@ -19139,9 +18022,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.157", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.157.tgz", - "integrity": "sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w==", + "version": "1.5.161", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz", + "integrity": "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==", "dev": true, "license": "ISC" }, @@ -19192,9 +18075,9 @@ } }, "node_modules/electron/node_modules/@types/node": { - "version": "20.17.50", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.50.tgz", - "integrity": "sha512-Mxiq0ULv/zo1OzOhwPqOA13I81CV/W3nvd3ChtQZRT5Cwz3cr0FKo/wMSsbTqL3EXpaBAEQhva2B8ByRkOIh9A==", + "version": "20.17.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.55.tgz", + "integrity": "sha512-ESpPDUEtW1a9nueMQtcTq/5iY/7osurPpBpFKH2VAyREKdzoFRRod6Oms0SSTfV7u52CcH7b6dFVnjfPD8fxWg==", "dev": true, "license": "MIT", "dependencies": { @@ -19395,9 +18278,9 @@ } }, "node_modules/es-abstract": { - "version": "1.23.10", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.10.tgz", - "integrity": "sha512-MtUbM072wlJNyeYAe0mhzrD+M6DIJa96CZAOBBrhDbgKnB4MApIKefcyAB1eOdYn8cUNZgvwBvEzdoAYsxgEIw==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, "license": "MIT", "dependencies": { @@ -19428,7 +18311,9 @@ "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", + "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", @@ -19443,6 +18328,7 @@ "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", @@ -19559,9 +18445,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", - "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -19572,30 +18458,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.23.0", - "@esbuild/android-arm": "0.23.0", - "@esbuild/android-arm64": "0.23.0", - "@esbuild/android-x64": "0.23.0", - "@esbuild/darwin-arm64": "0.23.0", - "@esbuild/darwin-x64": "0.23.0", - "@esbuild/freebsd-arm64": "0.23.0", - "@esbuild/freebsd-x64": "0.23.0", - "@esbuild/linux-arm": "0.23.0", - "@esbuild/linux-arm64": "0.23.0", - "@esbuild/linux-ia32": "0.23.0", - "@esbuild/linux-loong64": "0.23.0", - "@esbuild/linux-mips64el": "0.23.0", - "@esbuild/linux-ppc64": "0.23.0", - "@esbuild/linux-riscv64": "0.23.0", - "@esbuild/linux-s390x": "0.23.0", - "@esbuild/linux-x64": "0.23.0", - "@esbuild/netbsd-x64": "0.23.0", - "@esbuild/openbsd-arm64": "0.23.0", - "@esbuild/openbsd-x64": "0.23.0", - "@esbuild/sunos-x64": "0.23.0", - "@esbuild/win32-arm64": "0.23.0", - "@esbuild/win32-ia32": "0.23.0", - "@esbuild/win32-x64": "0.23.0" + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" } }, "node_modules/esbuild-register": { @@ -19612,9 +18499,9 @@ } }, "node_modules/esbuild-wasm": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.23.0.tgz", - "integrity": "sha512-6jP8UmWy6R6TUUV8bMuC3ZyZ6lZKI56x0tkxyCIqWwRRJ/DgeQKneh/Oid5EoGoPFLrGNkz47ZEtWAYuiY/u9g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.25.4.tgz", + "integrity": "sha512-2HlCS6rNvKWaSKhWaG/YIyRsTsL3gUrMP2ToZMBIjw9LM7vVcIs+rz8kE2vExvTJgvM8OKPqNpcHawY/BQc/qQ==", "dev": true, "license": "MIT", "bin": { @@ -20556,9 +19443,9 @@ "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -20566,7 +19453,7 @@ "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -20680,9 +19567,9 @@ } }, "node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -21900,46 +20787,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby/node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/globby/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globby/node_modules/ignore": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", - "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/globby/node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", @@ -22440,9 +21287,9 @@ } }, "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", @@ -22455,8 +21302,21 @@ "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", + "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/http-assert": { @@ -22805,9 +21665,9 @@ "license": "BSD-3-Clause" }, "node_modules/ignore": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", - "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", + "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", "dev": true, "license": "MIT", "engines": { @@ -22815,16 +21675,16 @@ } }, "node_modules/ignore-walk": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", - "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-7.0.0.tgz", + "integrity": "sha512-T4gbf83A4NH95zvhVYZc+qWocBBGlpzUXLPGurJggw/WIOwicfXJChLDP/iBZnN5WqROSu5Bm3hhle4z8a8YGQ==", "dev": true, "license": "ISC", "dependencies": { "minimatch": "^9.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/image-size": { @@ -23013,13 +21873,13 @@ "license": "ISC" }, "node_modules/ini": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", - "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/inject-stylesheet": { @@ -23483,6 +22343,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-network-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", @@ -25049,18 +23922,18 @@ } }, "node_modules/jest-preset-angular": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-14.1.1.tgz", - "integrity": "sha512-mWW2WlndHetTp4PQov05v7JE6HZQB5uTzGd+oW2RPH1OOTCLUKI8mSIU4DXCBJ4LDg5gIMMfqHsxT/Qmpu2dQQ==", + "version": "14.5.5", + "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-14.5.5.tgz", + "integrity": "sha512-PUykbixXEYSltKQE4450YuBiO8SMo2SwdGRHAdArRuV06Igq8gaLRVt9j8suj/4qtm2xRqoKnh5j52R0PfQxFw==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", "esbuild-wasm": ">=0.15.13", - "jest-environment-jsdom": "^29.0.0", - "jest-util": "^29.0.0", - "pretty-format": "^29.0.0", - "ts-jest": "^29.0.0" + "jest-environment-jsdom": "^29.7.0", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0", + "ts-jest": "^29.3.0" }, "engines": { "node": "^14.15.0 || >=16.10.0" @@ -25069,12 +23942,17 @@ "esbuild": ">=0.15.13" }, "peerDependencies": { - "@angular-devkit/build-angular": ">=15.0.0 <19.0.0", - "@angular/compiler-cli": ">=15.0.0 <19.0.0", - "@angular/core": ">=15.0.0 <19.0.0", - "@angular/platform-browser-dynamic": ">=15.0.0 <19.0.0", + "@angular/compiler-cli": ">=15.0.0 <20.0.0", + "@angular/core": ">=15.0.0 <20.0.0", + "@angular/platform-browser-dynamic": ">=15.0.0 <20.0.0", "jest": "^29.0.0", + "jsdom": ">=20.0.0", "typescript": ">=4.8" + }, + "peerDependenciesMeta": { + "jsdom": { + "optional": true + } } }, "node_modules/jest-preset-angular/node_modules/ansi-styles": { @@ -25112,6 +23990,69 @@ "dev": true, "license": "MIT" }, + "node_modules/jest-preset-angular/node_modules/ts-jest": { + "version": "29.3.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.4.tgz", + "integrity": "sha512-Iqbrm8IXOmV+ggWHOTEbjwyCf2xZlUMv5npExksXohL+tk8va4Fjhb+X2+Rt9NBmgO7bJ8WpnMLOwih/DnMlFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.2", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/jest-preset-angular/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/jest-process-manager": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/jest-process-manager/-/jest-process-manager-0.4.0.tgz", @@ -25858,13 +24799,13 @@ "license": "MIT" }, "node_modules/json-parse-even-better-errors": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", "dev": true, "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/json-schema-traverse": { @@ -26464,9 +25405,9 @@ } }, "node_modules/less": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", - "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.2.tgz", + "integrity": "sha512-tkuLHQlvWUTeQ3doAqnHbNn8T6WX1KA8yvbKG9x4VtKtIjHsVKQZCH11zRgAfbDAXC2UNIg/K9BYAAcEzUIrNg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -26862,9 +25803,9 @@ } }, "node_modules/listr2": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", - "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", + "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", "dev": true, "license": "MIT", "dependencies": { @@ -27050,29 +25991,30 @@ } }, "node_modules/lmdb": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.0.13.tgz", - "integrity": "sha512-UGe+BbaSUQtAMZobTb4nHvFMrmvuAQKSeaqAX2meTEQjfsbpl5sxdHD8T72OnwD4GU9uwNhYXIVe4QGs8N9Zyw==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.2.6.tgz", + "integrity": "sha512-SuHqzPl7mYStna8WRotY8XX/EUZBjjv3QyKIByeCLFfC9uXT/OIHByEcA07PzbMfQAM0KYJtLgtpMRlIe5dErQ==", "dev": true, "hasInstallScript": true, "license": "MIT", + "optional": true, "dependencies": { - "msgpackr": "^1.10.2", + "msgpackr": "^1.11.2", "node-addon-api": "^6.1.0", "node-gyp-build-optional-packages": "5.2.2", - "ordered-binary": "^1.4.1", + "ordered-binary": "^1.5.3", "weak-lru-cache": "^1.2.2" }, "bin": { "download-lmdb-prebuilds": "bin/download-prebuilds.js" }, "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "3.0.13", - "@lmdb/lmdb-darwin-x64": "3.0.13", - "@lmdb/lmdb-linux-arm": "3.0.13", - "@lmdb/lmdb-linux-arm64": "3.0.13", - "@lmdb/lmdb-linux-x64": "3.0.13", - "@lmdb/lmdb-win32-x64": "3.0.13" + "@lmdb/lmdb-darwin-arm64": "3.2.6", + "@lmdb/lmdb-darwin-x64": "3.2.6", + "@lmdb/lmdb-linux-arm": "3.2.6", + "@lmdb/lmdb-linux-arm64": "3.2.6", + "@lmdb/lmdb-linux-x64": "3.2.6", + "@lmdb/lmdb-win32-x64": "3.2.6" } }, "node_modules/lmdb/node_modules/node-addon-api": { @@ -27080,7 +26022,8 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/loader-runner": { "version": "4.3.0", @@ -27554,9 +26497,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "license": "MIT", "dependencies": { @@ -29065,9 +28008,9 @@ } }, "node_modules/mrmime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", - "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "dev": true, "license": "MIT", "engines": { @@ -29086,6 +28029,7 @@ "integrity": "sha512-uaff7RG9VIC4jacFW9xzL3jc0iM32DNHe4jYVycBcjUePT/Klnfj7pqtWJt9khvDFizmjN2TlYniYmSS2LIaZg==", "dev": true, "license": "MIT", + "optional": true, "optionalDependencies": { "msgpackr-extract": "^3.0.2" } @@ -29237,13 +28181,13 @@ } }, "node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/mz": { @@ -29379,30 +28323,6 @@ "@angular/platform-browser": ">=16.0.0-0" } }, - "node_modules/nice-napi": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", - "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "!win32" - ], - "dependencies": { - "node-addon-api": "^3.0.0", - "node-gyp-build": "^4.2.2" - } - }, - "node_modules/nice-napi/node_modules/node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -29505,28 +28425,28 @@ } }, "node_modules/node-gyp": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz", - "integrity": "sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.2.0.tgz", + "integrity": "sha512-T0S1zqskVUSxcsSTkAsLc7xCycrRYmtDHadDinzocrThjyQCn5kMlEBSj6H4qDbgsIOSLmmlRIeb0lZXj+UArA==", "dev": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^13.0.0", - "nopt": "^7.0.0", - "proc-log": "^4.1.0", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", "semver": "^7.3.5", - "tar": "^6.2.1", - "which": "^4.0.0" + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/node-gyp-build": { @@ -29546,6 +28466,7 @@ "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { "detect-libc": "^2.0.1" }, @@ -29556,36 +28477,36 @@ } }, "node_modules/node-gyp/node_modules/@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/node-gyp/node_modules/abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/node-gyp/node_modules/cacache": { - "version": "18.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", - "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/fs": "^3.1.0", + "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", @@ -29593,13 +28514,23 @@ "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/node-gyp/node_modules/fs-minipass": { @@ -29670,27 +28601,26 @@ "license": "ISC" }, "node_modules/node-gyp/node_modules/make-fetch-happen": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", - "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", + "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "proc-log": "^4.2.0", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "ssri": "^10.0.0" + "ssri": "^12.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/node-gyp/node_modules/minipass-collect": { @@ -29707,47 +28637,79 @@ } }, "node_modules/node-gyp/node_modules/minipass-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", - "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "minizlib": "^3.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" }, "optionalDependencies": { "encoding": "^0.1.13" } }, - "node_modules/node-gyp/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "node_modules/node-gyp/node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", "dev": true, "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + } + }, + "node_modules/node-gyp/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/node-gyp/node_modules/nopt": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", "dev": true, "license": "ISC", "dependencies": { - "abbrev": "^2.0.0" + "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/node-gyp/node_modules/path-scurry": { @@ -29768,58 +28730,76 @@ } }, "node_modules/node-gyp/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/node-gyp/node_modules/ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/node-gyp/node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "node_modules/node-gyp/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", "dev": true, "license": "ISC", "dependencies": { - "unique-slug": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=18" + } + }, + "node_modules/node-gyp/node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/node-gyp/node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/node-gyp/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "license": "ISC", "dependencies": { @@ -29829,7 +28809,17 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/node-int64": { @@ -29882,41 +28872,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/normalize-package-data": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/normalize-package-data/node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/normalize-package-data/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -29951,68 +28906,68 @@ } }, "node_modules/npm-bundled": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", - "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", + "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", "dev": true, "license": "ISC", "dependencies": { - "npm-normalize-package-bin": "^3.0.0" + "npm-normalize-package-bin": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-install-checks": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", - "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.1.tgz", + "integrity": "sha512-u6DCwbow5ynAX5BdiHQ9qvexme4U3qHW3MWe5NqH+NeBm0LbiH6zvGjNNew1fY+AZZUtVHbOPF3j7mJxbUzpXg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-package-arg": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", - "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", + "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", "dev": true, "license": "ISC", "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^4.0.0", + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" + "validate-npm-package-name": "^6.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-package-arg/node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^10.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-package-arg/node_modules/lru-cache": { @@ -30023,85 +28978,85 @@ "license": "ISC" }, "node_modules/npm-package-arg/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-packlist": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", - "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-9.0.0.tgz", + "integrity": "sha512-8qSayfmHJQTx3nJWYbbUmflpyarbLMBc6LCAjYsiGtXxDB68HaZpb8re6zeaLGxZzDuMdhsg70jryJe+RrItVQ==", "dev": true, "license": "ISC", "dependencies": { - "ignore-walk": "^6.0.4" + "ignore-walk": "^7.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-pick-manifest": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", - "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-10.0.0.tgz", + "integrity": "sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==", "dev": true, "license": "ISC", "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^11.0.0", + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-registry-fetch": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", - "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-18.0.2.tgz", + "integrity": "sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/redact": "^2.0.0", + "@npmcli/redact": "^3.0.0", "jsonparse": "^1.3.1", - "make-fetch-happen": "^13.0.0", + "make-fetch-happen": "^14.0.0", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minizlib": "^2.1.2", - "npm-package-arg": "^11.0.0", - "proc-log": "^4.0.0" + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-registry-fetch/node_modules/@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-registry-fetch/node_modules/cacache": { - "version": "18.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", - "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/fs": "^3.1.0", + "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", @@ -30109,13 +29064,23 @@ "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/npm-registry-fetch/node_modules/fs-minipass": { @@ -30176,27 +29141,26 @@ "license": "ISC" }, "node_modules/npm-registry-fetch/node_modules/make-fetch-happen": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", - "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", + "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "proc-log": "^4.2.0", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "ssri": "^10.0.0" + "ssri": "^12.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-registry-fetch/node_modules/minipass-collect": { @@ -30213,31 +29177,63 @@ } }, "node_modules/npm-registry-fetch/node_modules/minipass-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", - "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "minizlib": "^3.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" }, "optionalDependencies": { "encoding": "^0.1.13" } }, - "node_modules/npm-registry-fetch/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "node_modules/npm-registry-fetch/node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm-registry-fetch/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm-registry-fetch/node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm-registry-fetch/node_modules/path-scurry": { @@ -30258,52 +29254,80 @@ } }, "node_modules/npm-registry-fetch/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-registry-fetch/node_modules/ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm-registry-fetch/node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "node_modules/npm-registry-fetch/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", "dev": true, "license": "ISC", "dependencies": { - "unique-slug": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=18" + } + }, + "node_modules/npm-registry-fetch/node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-registry-fetch/node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/npm-run-path": { @@ -31112,7 +30136,8 @@ "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.3.tgz", "integrity": "sha512-oGFr3T+pYdTGJ+YFEILMpS3es+GiIbs9h/XQrclBXUtd44ey7XwfsMzM31f64I1SQOawDoDr/D823kNCADI8TA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/os-homedir": { "version": "1.0.2", @@ -31305,58 +30330,58 @@ "license": "BlueOak-1.0.0" }, "node_modules/pacote": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", - "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-20.0.0.tgz", + "integrity": "sha512-pRjC5UFwZCgx9kUFDVM9YEahv4guZ1nSLqwmWiLUnDbGsjs+U5w7z6Uc8HNR1a6x8qnu5y9xtGE6D1uAuYz+0A==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/package-json": "^5.1.0", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^8.0.0", - "cacache": "^18.0.0", + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", - "npm-package-arg": "^11.0.0", - "npm-packlist": "^8.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^17.0.0", - "proc-log": "^4.0.0", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "sigstore": "^2.2.0", - "ssri": "^10.0.0", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", "tar": "^6.1.11" }, "bin": { "pacote": "bin/index.js" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/pacote/node_modules/@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/pacote/node_modules/cacache": { - "version": "18.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", - "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/fs": "^3.1.0", + "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", @@ -31364,13 +30389,41 @@ "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/pacote/node_modules/cacache/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/pacote/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/pacote/node_modules/fs-minipass": { @@ -31443,6 +30496,48 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/pacote/node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/pacote/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pacote/node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pacote/node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -31461,52 +30556,62 @@ } }, "node_modules/pacote/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/pacote/node_modules/ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/pacote/node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", "dev": true, "license": "ISC", "dependencies": { - "unique-slug": "^4.0.0" + "unique-slug": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/pacote/node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/pacote/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/pako": { @@ -31963,13 +31068,13 @@ } }, "node_modules/piscina": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.6.1.tgz", - "integrity": "sha512-z30AwWGtQE+Apr+2WBZensP2lIvwoaMcOPkQlIEmSGMJNUvaYACylPYrQM6wSdUNJlnDVMSpLv7xTMJqlVshOA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.8.0.tgz", + "integrity": "sha512-EZJb+ZxDrQf3dihsUL7p42pjNyrNIFJCrRHPMgxu/svsj+P3xS3fuEWp7k2+rfsavfl1N0G29b1HGs7J0m8rZA==", "dev": true, "license": "MIT", "optionalDependencies": { - "nice-napi": "^1.0.2" + "@napi-rs/nice": "^1.0.1" } }, "node_modules/pkce-challenge": { @@ -33623,18 +32728,21 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -33868,13 +32976,13 @@ } }, "node_modules/rollup": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", - "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", + "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -33884,29 +32992,32 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.22.4", - "@rollup/rollup-android-arm64": "4.22.4", - "@rollup/rollup-darwin-arm64": "4.22.4", - "@rollup/rollup-darwin-x64": "4.22.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", - "@rollup/rollup-linux-arm-musleabihf": "4.22.4", - "@rollup/rollup-linux-arm64-gnu": "4.22.4", - "@rollup/rollup-linux-arm64-musl": "4.22.4", - "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", - "@rollup/rollup-linux-riscv64-gnu": "4.22.4", - "@rollup/rollup-linux-s390x-gnu": "4.22.4", - "@rollup/rollup-linux-x64-gnu": "4.22.4", - "@rollup/rollup-linux-x64-musl": "4.22.4", - "@rollup/rollup-win32-arm64-msvc": "4.22.4", - "@rollup/rollup-win32-ia32-msvc": "4.22.4", - "@rollup/rollup-win32-x64-msvc": "4.22.4", + "@rollup/rollup-android-arm-eabi": "4.34.8", + "@rollup/rollup-android-arm64": "4.34.8", + "@rollup/rollup-darwin-arm64": "4.34.8", + "@rollup/rollup-darwin-x64": "4.34.8", + "@rollup/rollup-freebsd-arm64": "4.34.8", + "@rollup/rollup-freebsd-x64": "4.34.8", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", + "@rollup/rollup-linux-arm-musleabihf": "4.34.8", + "@rollup/rollup-linux-arm64-gnu": "4.34.8", + "@rollup/rollup-linux-arm64-musl": "4.34.8", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", + "@rollup/rollup-linux-riscv64-gnu": "4.34.8", + "@rollup/rollup-linux-s390x-gnu": "4.34.8", + "@rollup/rollup-linux-x64-gnu": "4.34.8", + "@rollup/rollup-linux-x64-musl": "4.34.8", + "@rollup/rollup-win32-arm64-msvc": "4.34.8", + "@rollup/rollup-win32-ia32-msvc": "4.34.8", + "@rollup/rollup-win32-x64-msvc": "4.34.8", "fsevents": "~2.3.2" } }, "node_modules/rollup/node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true, "license": "MIT" }, @@ -34756,21 +33867,21 @@ "license": "Apache-2.0" }, "node_modules/sigstore": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", - "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-3.1.0.tgz", + "integrity": "sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.3.2", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "@sigstore/sign": "^2.3.2", - "@sigstore/tuf": "^2.3.4", - "@sigstore/verify": "^1.2.1" + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "@sigstore/sign": "^3.1.0", + "@sigstore/tuf": "^3.1.0", + "@sigstore/verify": "^2.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/simple-concat": { @@ -35328,6 +34439,20 @@ "graceful-fs": "^4.1.3" } }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/storybook": { "version": "8.6.12", "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.12.tgz", @@ -36100,9 +35225,9 @@ } }, "node_modules/terser": { - "version": "5.31.6", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", - "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", + "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -36316,9 +35441,9 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -36365,9 +35490,9 @@ } }, "node_modules/tldts-core": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.7.tgz", - "integrity": "sha512-ECqb8imSroX1UmUuhRBNPkkmtZ8mHEenieim80UVxG0M5wXVjY2Fp2tYXCPvk+nLy1geOhFpeD5YQhM/gF63Jg==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.8.tgz", + "integrity": "sha512-Ze39mm8EtocSXPbH6cv5rDeBBhehp8OLxWJKZXLEyv2dKMlblJsoAw2gmA0ZaU6iOwNlCZ4LrmaTW1reUQEmJw==", "license": "MIT" }, "node_modules/tmp": { @@ -36718,9 +35843,9 @@ } }, "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/tsscmp": { @@ -36775,41 +35900,41 @@ "license": "0BSD" }, "node_modules/tuf-js": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", - "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-3.0.1.tgz", + "integrity": "sha512-+68OP1ZzSF84rTckf3FA95vJ1Zlx/uaXyiiKyPd1pA4rZNkpEvDAKmsu1xUSmbF/chCRYgZ6UZkDwC7PmzmAyA==", "dev": true, "license": "MIT", "dependencies": { - "@tufjs/models": "2.0.1", - "debug": "^4.3.4", - "make-fetch-happen": "^13.0.1" + "@tufjs/models": "3.0.1", + "debug": "^4.3.6", + "make-fetch-happen": "^14.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/tuf-js/node_modules/@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/tuf-js/node_modules/cacache": { - "version": "18.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", - "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/fs": "^3.1.0", + "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", @@ -36817,13 +35942,23 @@ "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/tuf-js/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/tuf-js/node_modules/fs-minipass": { @@ -36884,27 +36019,26 @@ "license": "ISC" }, "node_modules/tuf-js/node_modules/make-fetch-happen": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", - "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", + "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "proc-log": "^4.2.0", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "ssri": "^10.0.0" + "ssri": "^12.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/tuf-js/node_modules/minipass-collect": { @@ -36921,31 +36055,63 @@ } }, "node_modules/tuf-js/node_modules/minipass-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", - "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "minizlib": "^3.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" }, "optionalDependencies": { "encoding": "^0.1.13" } }, - "node_modules/tuf-js/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "node_modules/tuf-js/node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/tuf-js/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tuf-js/node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/tuf-js/node_modules/path-scurry": { @@ -36966,52 +36132,80 @@ } }, "node_modules/tuf-js/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/tuf-js/node_modules/ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/tuf-js/node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "node_modules/tuf-js/node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", "dev": true, "license": "ISC", "dependencies": { - "unique-slug": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=18" + } + }, + "node_modules/tuf-js/node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/tuf-js/node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/tuf-js/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/tunnel-agent": { @@ -37590,9 +36784,9 @@ } }, "node_modules/unrs-resolver": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.2.tgz", - "integrity": "sha512-BBKpaylOW8KbHsu378Zky/dGh4ckT/4NW/0SHRABdqRLcQJ2dAOjDo9g97p04sWflm0kqPqpUatxReNV/dqI5A==", + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.8.tgz", + "integrity": "sha512-2zsXwyOXmCX9nGz4vhtZRYhe30V78heAv+KDc21A/KMdovGHbZcixeD5JHEF0DrFXzdytwuzYclcPbvp8A3Jlw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -37600,26 +36794,26 @@ "napi-postinstall": "^0.2.2" }, "funding": { - "url": "https://github.com/sponsors/JounQin" + "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-darwin-arm64": "1.7.2", - "@unrs/resolver-binding-darwin-x64": "1.7.2", - "@unrs/resolver-binding-freebsd-x64": "1.7.2", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.7.2", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.7.2", - "@unrs/resolver-binding-linux-arm64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-arm64-musl": "1.7.2", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-riscv64-musl": "1.7.2", - "@unrs/resolver-binding-linux-s390x-gnu": "1.7.2", - "@unrs/resolver-binding-linux-x64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-x64-musl": "1.7.2", - "@unrs/resolver-binding-wasm32-wasi": "1.7.2", - "@unrs/resolver-binding-win32-arm64-msvc": "1.7.2", - "@unrs/resolver-binding-win32-ia32-msvc": "1.7.2", - "@unrs/resolver-binding-win32-x64-msvc": "1.7.2" + "@unrs/resolver-binding-darwin-arm64": "1.7.8", + "@unrs/resolver-binding-darwin-x64": "1.7.8", + "@unrs/resolver-binding-freebsd-x64": "1.7.8", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.7.8", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.7.8", + "@unrs/resolver-binding-linux-arm64-gnu": "1.7.8", + "@unrs/resolver-binding-linux-arm64-musl": "1.7.8", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.7.8", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.7.8", + "@unrs/resolver-binding-linux-riscv64-musl": "1.7.8", + "@unrs/resolver-binding-linux-s390x-gnu": "1.7.8", + "@unrs/resolver-binding-linux-x64-gnu": "1.7.8", + "@unrs/resolver-binding-linux-x64-musl": "1.7.8", + "@unrs/resolver-binding-wasm32-wasi": "1.7.8", + "@unrs/resolver-binding-win32-arm64-msvc": "1.7.8", + "@unrs/resolver-binding-win32-ia32-msvc": "1.7.8", + "@unrs/resolver-binding-win32-x64-msvc": "1.7.8" } }, "node_modules/update-browserslist-db": { @@ -37799,13 +36993,13 @@ } }, "node_modules/validate-npm-package-name": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.0.tgz", + "integrity": "sha512-d7KLgL1LD3U3fgnvWEY1cQXoO/q6EQ1BSz48Sa149V/5zVTAbgmZIpyI8TRi6U9/JNyeYLlTKsEMPtLC27RFUg==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/vary": { @@ -37879,21 +37073,25 @@ } }, "node_modules/vite": { - "version": "5.4.19", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", - "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -37902,19 +37100,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -37935,30 +37139,19 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "node_modules/vite/node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", + "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", "cpu": [ "arm" ], @@ -37968,14 +37161,12 @@ "os": [ "android" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "node_modules/vite/node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", + "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", "cpu": [ "arm64" ], @@ -37985,31 +37176,12 @@ "os": [ "android" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "node_modules/vite/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", + "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", "cpu": [ "arm64" ], @@ -38019,14 +37191,12 @@ "os": [ "darwin" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "node_modules/vite/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", + "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", "cpu": [ "x64" ], @@ -38036,14 +37206,12 @@ "os": [ "darwin" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "node_modules/vite/node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", + "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", "cpu": [ "arm64" ], @@ -38053,14 +37221,12 @@ "os": [ "freebsd" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "node_modules/vite/node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", + "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", "cpu": [ "x64" ], @@ -38070,14 +37236,12 @@ "os": [ "freebsd" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "node_modules/vite/node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", + "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", "cpu": [ "arm" ], @@ -38087,14 +37251,27 @@ "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "node_modules/vite/node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", + "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/vite/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", + "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", "cpu": [ "arm64" ], @@ -38104,16 +37281,14 @@ "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "node_modules/vite/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", + "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", "cpu": [ - "ia32" + "arm64" ], "dev": true, "license": "MIT", @@ -38121,14 +37296,12 @@ "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "node_modules/vite/node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", + "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", "cpu": [ "loong64" ], @@ -38138,31 +37311,12 @@ "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "node_modules/vite/node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", + "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", "cpu": [ "ppc64" ], @@ -38172,14 +37326,12 @@ "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "node_modules/vite/node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", + "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", "cpu": [ "riscv64" ], @@ -38189,14 +37341,12 @@ "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "node_modules/vite/node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", + "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", "cpu": [ "s390x" ], @@ -38206,14 +37356,12 @@ "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "node_modules/vite/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", + "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", "cpu": [ "x64" ], @@ -38223,14 +37371,12 @@ "os": [ "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "node_modules/vite/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", + "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", "cpu": [ "x64" ], @@ -38238,50 +37384,14 @@ "license": "MIT", "optional": true, "os": [ - "netbsd" + "linux" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "node_modules/vite/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", + "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", "cpu": [ "arm64" ], @@ -38291,14 +37401,12 @@ "os": [ "win32" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "node_modules/vite/node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", + "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", "cpu": [ "ia32" ], @@ -38308,14 +37416,12 @@ "os": [ "win32" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "node_modules/vite/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", + "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", "cpu": [ "x64" ], @@ -38325,47 +37431,47 @@ "os": [ "win32" ], - "engines": { - "node": ">=12" - } + "peer": true }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "node_modules/vite/node_modules/rollup": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", + "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", "dev": true, - "hasInstallScript": true, "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "1.0.7" + }, "bin": { - "esbuild": "bin/esbuild" + "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=12" + "node": ">=18.0.0", + "npm": ">=8.0.0" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@rollup/rollup-android-arm-eabi": "4.41.1", + "@rollup/rollup-android-arm64": "4.41.1", + "@rollup/rollup-darwin-arm64": "4.41.1", + "@rollup/rollup-darwin-x64": "4.41.1", + "@rollup/rollup-freebsd-arm64": "4.41.1", + "@rollup/rollup-freebsd-x64": "4.41.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", + "@rollup/rollup-linux-arm-musleabihf": "4.41.1", + "@rollup/rollup-linux-arm64-gnu": "4.41.1", + "@rollup/rollup-linux-arm64-musl": "4.41.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-musl": "4.41.1", + "@rollup/rollup-linux-s390x-gnu": "4.41.1", + "@rollup/rollup-linux-x64-gnu": "4.41.1", + "@rollup/rollup-linux-x64-musl": "4.41.1", + "@rollup/rollup-win32-arm64-msvc": "4.41.1", + "@rollup/rollup-win32-ia32-msvc": "4.41.1", + "@rollup/rollup-win32-x64-msvc": "4.41.1", + "fsevents": "~2.3.2" } }, "node_modules/w3c-xmlserializer": { @@ -38531,9 +37637,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", - "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "dev": true, "license": "MIT", "dependencies": { @@ -38568,7 +37674,8 @@ "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/webidl-conversions": { "version": "7.0.0", @@ -39379,9 +38486,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.0.tgz", + "integrity": "sha512-77R0RDmJfj9dyv5p3bM5pOHa+X8/ZkO9c7kpDstigkC4nIDobadsfSGCwB4bKhMVxqAok8tajaoR8rirM7+VFQ==", "dev": true, "license": "MIT", "engines": { @@ -39418,9 +38525,9 @@ "license": "MIT" }, "node_modules/webpack/node_modules/browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", "dev": true, "funding": [ { @@ -39438,8 +38545,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001716", - "electron-to-chromium": "^1.5.149", + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -40070,9 +39177,9 @@ } }, "node_modules/zod": { - "version": "3.25.23", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.23.tgz", - "integrity": "sha512-Od2bdMosahjSrSgJtakrwjMDb1zM1A3VIHCPGveZt/3/wlrTWBya2lmEh2OYe4OIu8mPTmmr0gnLHIWQXdtWBg==", + "version": "3.25.42", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.42.tgz", + "integrity": "sha512-PcALTLskaucbeHc41tU/xfjfhcz8z0GdhhDcSgrCTmSazUuqnYqiXO63M0QUBVwpBlsLsNVn5qHSC5Dw3KZvaQ==", "dev": true, "license": "MIT", "funding": { @@ -40090,9 +39197,9 @@ } }, "node_modules/zone.js": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz", - "integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.0.tgz", + "integrity": "sha512-9oxn0IIjbCZkJ67L+LkhYWRyAy7axphb3VgE2MBDlOqnmHMPWGYMxJxBYFueFq/JGY2GMwS0rU+UCLunEmy5UA==", "license": "MIT" }, "node_modules/zwitch": { diff --git a/package.json b/package.json index 0eaf07ee6d7..e9d0ad6b03f 100644 --- a/package.json +++ b/package.json @@ -38,10 +38,10 @@ "libs/**/*" ], "devDependencies": { - "@angular-devkit/build-angular": "18.2.19", - "@angular-eslint/schematics": "18.4.3", - "@angular/cli": "18.2.19", - "@angular/compiler-cli": "18.2.13", + "@angular-devkit/build-angular": "19.2.14", + "@angular-eslint/schematics": "19.6.0", + "@angular/cli": "19.2.14", + "@angular/compiler-cli": "19.2.14", "@babel/core": "7.24.9", "@babel/preset-env": "7.24.8", "@compodoc/compodoc": "1.1.26", @@ -49,7 +49,7 @@ "@electron/rebuild": "3.7.2", "@eslint/compat": "1.2.9", "@lit-labs/signals": "0.1.2", - "@ngtools/webpack": "18.2.19", + "@ngtools/webpack": "19.2.14", "@storybook/addon-a11y": "8.6.12", "@storybook/addon-actions": "8.6.12", "@storybook/addon-designs": "8.2.1", @@ -86,7 +86,7 @@ "@typescript-eslint/utils": "8.31.0", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.16.1", - "angular-eslint": "18.4.3", + "angular-eslint": "19.6.0", "autoprefixer": "10.4.21", "axe-playwright": "2.1.0", "babel-loader": "9.2.1", @@ -118,7 +118,7 @@ "jest-diff": "29.7.0", "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", - "jest-preset-angular": "14.1.1", + "jest-preset-angular": "14.5.5", "json5": "2.2.3", "lint-staged": "16.0.0", "mini-css-extract-plugin": "2.9.2", @@ -151,15 +151,15 @@ "webpack-node-externals": "3.0.0" }, "dependencies": { - "@angular/animations": "18.2.13", - "@angular/cdk": "18.2.14", - "@angular/common": "18.2.13", - "@angular/compiler": "18.2.13", - "@angular/core": "18.2.13", - "@angular/forms": "18.2.13", - "@angular/platform-browser": "18.2.13", - "@angular/platform-browser-dynamic": "18.2.13", - "@angular/router": "18.2.13", + "@angular/animations": "19.2.14", + "@angular/cdk": "19.2.18", + "@angular/common": "19.2.14", + "@angular/compiler": "19.2.14", + "@angular/core": "19.2.14", + "@angular/forms": "19.2.14", + "@angular/platform-browser": "19.2.14", + "@angular/platform-browser-dynamic": "19.2.14", + "@angular/router": "19.2.14", "@bitwarden/sdk-internal": "0.2.0-main.177", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", @@ -167,7 +167,7 @@ "@koa/router": "13.1.0", "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", - "@ng-select/ng-select": "13.9.1", + "@ng-select/ng-select": "14.9.0", "argon2": "0.41.1", "argon2-browser": "1.18.0", "big-integer": "1.6.52", @@ -205,7 +205,7 @@ "tabbable": "6.2.0", "tldts": "7.0.1", "utf-8-validate": "6.0.5", - "zone.js": "0.14.10", + "zone.js": "0.15.0", "zxcvbn": "4.4.2" }, "overrides": { @@ -216,14 +216,10 @@ "eslint": "$eslint" }, "tailwindcss": "$tailwindcss", - "@storybook/angular": { - "zone.js": "$zone.js" - }, "parse5": "7.2.1", "react": "18.3.1", "react-dom": "18.3.1", - "@types/react": "18.3.20", - "replacestream": "4.0.3" + "@types/react": "18.3.20" }, "lint-staged": { "*": "prettier --cache --ignore-unknown --write", diff --git a/scripts/test-types.js b/scripts/test-types.js index 9534558af30..f71f236c607 100644 --- a/scripts/test-types.js +++ b/scripts/test-types.js @@ -19,7 +19,7 @@ function getFiles(dir) { const files = getFiles(path.join(__dirname, "..", "libs")) .filter((file) => { const name = path.basename(file); - return name === "tsconfig.spec.json"; + return name === "tsconfig.json"; }) .filter((path) => { // Exclude shared since it's not actually a library From 4ceba4841b7878f0c18cd9c0f2497d72ce9faf41 Mon Sep 17 00:00:00 2001 From: Tom <144813356+ttalty@users.noreply.github.com> Date: Mon, 2 Jun 2025 13:35:14 -0400 Subject: [PATCH 212/245] Removing critical applications feature flag and logic (#14889) --- .../access-intelligence/all-applications.component.html | 2 -- .../dirt/access-intelligence/all-applications.component.ts | 6 ------ .../app-table-row-scrollable.component.html | 4 ++-- .../app-table-row-scrollable.component.ts | 1 - .../critical-applications.component.html | 1 - .../dirt/access-intelligence/risk-insights.component.html | 2 +- .../app/dirt/access-intelligence/risk-insights.component.ts | 6 ------ libs/common/src/enums/feature-flag.enum.ts | 2 -- 8 files changed, 3 insertions(+), 21 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html index 0dfe55bed48..d383d1153c7 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html @@ -59,7 +59,6 @@ type="button" [buttonType]="'primary'" bitButton - *ngIf="isCriticalAppsFeatureEnabled" [disabled]="!selectedUrls.size" [loading]="markingAsCritical" (click)="markAppsAsCritical()" @@ -74,7 +73,6 @@ [showRowCheckBox]="true" [showRowMenuForCriticalApps]="false" [selectedUrls]="selectedUrls" - [isCriticalAppsFeatureEnabled]="isCriticalAppsFeatureEnabled" [isDrawerIsOpenForThisRecord]="isDrawerOpenForTableRow" [checkboxChange]="onCheckboxChange" [showAppAtRiskMembers]="showAppAtRiskMembers" diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts index 17525b4ed00..8225571cb98 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts @@ -21,7 +21,6 @@ import { import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -74,13 +73,8 @@ export class AllApplicationsComponent implements OnInit { destroyRef = inject(DestroyRef); isLoading$: Observable<boolean> = of(false); - isCriticalAppsFeatureEnabled = false; async ngOnInit() { - this.isCriticalAppsFeatureEnabled = await this.configService.getFeatureFlag( - FeatureFlag.CriticalApps, - ); - const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId") ?? ""; const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html index 10dbb179519..383780b450c 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html @@ -1,7 +1,7 @@ <ng-container> <bit-table-scroll [dataSource]="dataSource" [rowSize]="53"> <ng-container header> - <th *ngIf="isCriticalAppsFeatureEnabled"></th> + <th></th> <th bitCell></th> <th bitSortable="applicationName" bitCell>{{ "application" | i18n }}</th> <th bitSortable="atRiskPasswordCount" bitCell>{{ "atRiskPasswords" | i18n }}</th> @@ -12,7 +12,7 @@ <ng-template bitRowDef let-row> <td bitCell - *ngIf="isCriticalAppsFeatureEnabled && showRowCheckBox" + *ngIf="showRowCheckBox" [ngClass]="{ 'tw-bg-primary-100': isDrawerIsOpenForThisRecord(row.applicationName) }" > <input diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts index 052288013c4..4d373072733 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts @@ -18,7 +18,6 @@ export class AppTableRowScrollableComponent { @Input() showRowMenuForCriticalApps: boolean = false; @Input() showRowCheckBox: boolean = false; @Input() selectedUrls: Set<string> = new Set<string>(); - @Input() isCriticalAppsFeatureEnabled: boolean = false; @Input() isDrawerIsOpenForThisRecord!: (applicationName: string) => boolean; @Input() showAppAtRiskMembers!: (applicationName: string) => void; @Input() unmarkAsCriticalApp!: (applicationName: string) => void; diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.html index a74c1964d9a..4e2b4e5c404 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.html @@ -82,7 +82,6 @@ [dataSource]="dataSource" [showRowCheckBox]="false" [showRowMenuForCriticalApps]="true" - [isCriticalAppsFeatureEnabled]="true" [isDrawerIsOpenForThisRecord]="isDrawerOpenForTableRow" [showAppAtRiskMembers]="showAppAtRiskMembers" [unmarkAsCriticalApp]="unmarkAsCriticalApp" diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html index f759e483bd0..c9408b806ff 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html @@ -38,7 +38,7 @@ <bit-tab label="{{ 'allApplicationsWithCount' | i18n: appsCount }}"> <tools-all-applications></tools-all-applications> </bit-tab> - <bit-tab *ngIf="isCriticalAppsFeatureEnabled"> + <bit-tab> <ng-template bitTabLabel> <i class="bwi bwi-star"></i> {{ "criticalApplicationsWithCount" | i18n: (criticalApps$ | async)?.length ?? 0 }} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts index e47e1851099..54c02617f00 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts @@ -15,7 +15,6 @@ import { DrawerType, PasswordHealthReportApplicationsResponse, } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags"; import { OrganizationId } from "@bitwarden/common/types/guid"; @@ -70,7 +69,6 @@ export class RiskInsightsComponent implements OnInit { dataLastUpdated: Date = new Date(); - isCriticalAppsFeatureEnabled: boolean = false; criticalApps$: Observable<PasswordHealthReportApplicationsResponse[]> = new Observable(); showDebugTabs: boolean = false; @@ -100,10 +98,6 @@ export class RiskInsightsComponent implements OnInit { } async ngOnInit() { - this.isCriticalAppsFeatureEnabled = await this.configService.getFeatureFlag( - FeatureFlag.CriticalApps, - ); - this.showDebugTabs = devFlagEnabled("showRiskInsightsDebug"); this.route.paramMap diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index b3b8cc99926..dd7c5834a12 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -36,7 +36,6 @@ export enum FeatureFlag { UseOrganizationWarningsService = "use-organization-warnings-service", /* Data Insights and Reporting */ - CriticalApps = "pm-14466-risk-insights-critical-application", EnableRiskInsightsNotifications = "enable-risk-insights-notifications", /* Key Management */ @@ -93,7 +92,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.MacOsNativeCredentialSync]: FALSE, /* Data Insights and Reporting */ - [FeatureFlag.CriticalApps]: FALSE, [FeatureFlag.EnableRiskInsightsNotifications]: FALSE, /* Tools */ From f77bd8c5546004a5336f1687a892e6195a2a06da Mon Sep 17 00:00:00 2001 From: Daniel Riera <driera@livefront.com> Date: Mon, 2 Jun 2025 13:37:28 -0400 Subject: [PATCH 213/245] PM-16653 remove idp auto submit login step 1 (#14847) * PM-16653 remove idp auto submit login step 1 * remove config service mock * remove configservice from main.ts * edit test describes to be accurate * Update apps/browser/src/autofill/background/auto-submit-login.background.ts Co-authored-by: Jonathan Prusik <jprusik@users.noreply.github.com> --------- Co-authored-by: Jonathan Prusik <jprusik@users.noreply.github.com> --- .../auto-submit-login.background.spec.ts | 11 ++----- .../auto-submit-login.background.ts | 29 +++++++------------ .../browser/src/background/main.background.ts | 1 - .../bit-web/src/app/app.component.ts | 12 ++------ libs/common/src/enums/feature-flag.enum.ts | 2 -- 5 files changed, 15 insertions(+), 40 deletions(-) diff --git a/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts b/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts index 9f197b02193..373354b4c54 100644 --- a/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts +++ b/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts @@ -5,7 +5,6 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -35,7 +34,6 @@ describe("AutoSubmitLoginBackground", () => { let scriptInjectorService: MockProxy<ScriptInjectorService>; let authStatus$: BehaviorSubject<AuthenticationStatus>; let authService: MockProxy<AuthService>; - let configService: MockProxy<ConfigService>; let platformUtilsService: MockProxy<PlatformUtilsService>; let policyDetails: MockProxy<Policy>; let automaticAppLogInPolicy$: BehaviorSubject<Policy[]>; @@ -56,9 +54,6 @@ describe("AutoSubmitLoginBackground", () => { authStatus$ = new BehaviorSubject(AuthenticationStatus.Unlocked); authService = mock<AuthService>(); authService.activeAccountStatus$ = authStatus$; - configService = mock<ConfigService>({ - getFeatureFlag: jest.fn().mockResolvedValue(true), - }); platformUtilsService = mock<PlatformUtilsService>(); policyDetails = mock<Policy>({ enabled: true, @@ -78,7 +73,6 @@ describe("AutoSubmitLoginBackground", () => { autofillService, scriptInjectorService, authService, - configService, platformUtilsService, policyService, accountService, @@ -89,7 +83,7 @@ describe("AutoSubmitLoginBackground", () => { jest.clearAllMocks(); }); - describe("when the AutoSubmitLoginBackground feature is disabled", () => { + describe("when conditions prevent auto-submit policy activation", () => { it("destroys all event listeners when the AutomaticAppLogIn policy is not enabled", async () => { automaticAppLogInPolicy$.next([mock<Policy>({ ...policyDetails, enabled: false })]); @@ -115,7 +109,7 @@ describe("AutoSubmitLoginBackground", () => { }); }); - describe("when the AutoSubmitLoginBackground feature is enabled", () => { + describe("when the AutomaticAppLogIn policy is valid and active", () => { let webRequestDetails: chrome.webRequest.WebRequestBodyDetails; describe("starting the auto-submit login workflow", () => { @@ -268,7 +262,6 @@ describe("AutoSubmitLoginBackground", () => { autofillService, scriptInjectorService, authService, - configService, platformUtilsService, policyService, accountService, diff --git a/apps/browser/src/autofill/background/auto-submit-login.background.ts b/apps/browser/src/autofill/background/auto-submit-login.background.ts index bce876e8f82..dcafe21b63c 100644 --- a/apps/browser/src/autofill/background/auto-submit-login.background.ts +++ b/apps/browser/src/autofill/background/auto-submit-login.background.ts @@ -10,8 +10,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -42,7 +40,6 @@ export class AutoSubmitLoginBackground implements AutoSubmitLoginBackgroundAbstr private autofillService: AutofillService, private scriptInjectorService: ScriptInjectorService, private authService: AuthService, - private configService: ConfigService, private platformUtilsService: PlatformUtilsService, private policyService: PolicyService, private accountService: AccountService, @@ -51,25 +48,19 @@ export class AutoSubmitLoginBackground implements AutoSubmitLoginBackgroundAbstr } /** - * Initializes the auto-submit login policy. Will return early if - * the feature flag is not set. If the policy is not enabled, it + * Initializes the auto-submit login policy. If the policy is not enabled, it * will trigger a removal of any established listeners. */ async init() { - const featureFlagEnabled = await this.configService.getFeatureFlag( - FeatureFlag.IdpAutoSubmitLogin, - ); - if (featureFlagEnabled) { - this.accountService.activeAccount$ - .pipe( - getUserId, - switchMap((userId) => - this.policyService.policiesByType$(PolicyType.AutomaticAppLogIn, userId), - ), - getFirstPolicy, - ) - .subscribe(this.handleAutoSubmitLoginPolicySubscription.bind(this)); - } + this.accountService.activeAccount$ + .pipe( + getUserId, + switchMap((userId) => + this.policyService.policiesByType$(PolicyType.AutomaticAppLogIn, userId), + ), + getFirstPolicy, + ) + .subscribe(this.handleAutoSubmitLoginPolicySubscription.bind(this)); } /** diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 6ae6f7f7eb7..5225ebc0fb1 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1238,7 +1238,6 @@ export default class MainBackground { this.autofillService, this.scriptInjectorService, this.authService, - this.configService, this.platformUtilsService, this.policyService, this.accountService, diff --git a/bitwarden_license/bit-web/src/app/app.component.ts b/bitwarden_license/bit-web/src/app/app.component.ts index 2d0dfd967a1..ca6a5ea8f62 100644 --- a/bitwarden_license/bit-web/src/app/app.component.ts +++ b/bitwarden_license/bit-web/src/app/app.component.ts @@ -1,6 +1,5 @@ import { Component, OnInit } from "@angular/core"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { AppComponent as BaseAppComponent } from "@bitwarden/web-vault/app/app.component"; import { ActivateAutofillPolicy } from "./admin-console/policies/activate-autofill.component"; @@ -25,13 +24,8 @@ export class AppComponent extends BaseAppComponent implements OnInit { new ActivateAutofillPolicy(), ]); - this.configService.getFeatureFlag(FeatureFlag.IdpAutoSubmitLogin).then((enabled) => { - if ( - enabled && - !this.policyListService.getPolicies().some((p) => p instanceof AutomaticAppLoginPolicy) - ) { - this.policyListService.addPolicies([new AutomaticAppLoginPolicy()]); - } - }); + if (!this.policyListService.getPolicies().some((p) => p instanceof AutomaticAppLoginPolicy)) { + this.policyListService.addPolicies([new AutomaticAppLoginPolicy()]); + } } } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index dd7c5834a12..dbddc426e73 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -22,7 +22,6 @@ export enum FeatureFlag { /* Autofill */ BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain", EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill", - IdpAutoSubmitLogin = "idp-auto-submit-login", NotificationRefresh = "notification-refresh", UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", MacOsNativeCredentialSync = "macos-native-credential-sync", @@ -86,7 +85,6 @@ export const DefaultFeatureFlagValue = { /* Autofill */ [FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE, [FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE, - [FeatureFlag.IdpAutoSubmitLogin]: FALSE, [FeatureFlag.NotificationRefresh]: FALSE, [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, [FeatureFlag.MacOsNativeCredentialSync]: FALSE, From 6107d7d3da064410f659d8e271c3096770d3ef13 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Mon, 2 Jun 2025 10:47:32 -0700 Subject: [PATCH 214/245] add taskService.listenForTaskNotifications to init service (#14985) --- apps/web/src/app/core/init.service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 43547ff5d57..39dc6e1edd4 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -19,6 +19,7 @@ import { NotificationsService } from "@bitwarden/common/platform/notifications"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; +import { TaskService } from "@bitwarden/common/vault/tasks"; import { KeyService as KeyServiceAbstraction } from "@bitwarden/key-management"; import { VersionService } from "../platform/version.service"; @@ -43,6 +44,7 @@ export class InitService { private sdkLoadService: SdkLoadService, private configService: ConfigService, private bulkEncryptService: BulkEncryptService, + private taskService: TaskService, @Inject(DOCUMENT) private document: Document, ) {} @@ -75,6 +77,7 @@ export class InitService { this.themingService.applyThemeChangesTo(this.document); this.versionService.applyVersionToWindow(); void this.ipcService.init(); + this.taskService.listenForTaskNotifications(); const containerService = new ContainerService(this.keyService, this.encryptService); containerService.attachToGlobal(this.win); From 26fb7effd3e3f5d6824a12ec67462d336747e456 Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Mon, 2 Jun 2025 20:03:04 +0200 Subject: [PATCH 215/245] Remove standalone true from platform and UIF (#15032) Remove standalone: true from every instance since it's the default as of Angular 19. --- .../platform/popup/components/pop-out.component.ts | 1 - apps/browser/src/platform/popup/header.component.ts | 1 - .../platform/popup/layout/popup-back.directive.ts | 1 - .../platform/popup/layout/popup-footer.component.ts | 1 - .../platform/popup/layout/popup-header.component.ts | 1 - .../platform/popup/layout/popup-layout.stories.ts | 13 ------------- .../platform/popup/layout/popup-page.component.ts | 1 - .../popup/layout/popup-tab-navigation.component.ts | 1 - .../desktop-sync-verification-dialog.component.ts | 1 - .../browser-sync-verification-dialog.component.ts | 1 - .../app/components/user-verification.component.ts | 1 - .../verify-native-messaging-dialog.component.ts | 1 - apps/desktop/src/app/layout/nav.component.ts | 1 - .../src/platform/components/approve-ssh-request.ts | 1 - .../src/app/components/dynamic-avatar.component.ts | 1 - .../environment-selector.component.ts | 1 - .../src/app/layouts/frontend-layout.component.ts | 1 - .../src/app/layouts/header/web-header.stories.ts | 1 - .../layouts/org-switcher/org-switcher.component.ts | 1 - apps/web/src/app/layouts/toggle-width.component.ts | 1 - apps/web/src/app/layouts/user-layout.component.ts | 1 - apps/web/src/app/layouts/web-layout.component.ts | 1 - apps/web/src/app/layouts/web-side-nav.component.ts | 1 - apps/web/src/app/settings/domain-rules.component.ts | 1 - apps/web/src/app/settings/preferences.component.ts | 1 - .../account-fingerprint.component.ts | 1 - libs/angular/src/directives/text-drag.directive.ts | 1 - libs/angular/src/pipes/pluralize.pipe.ts | 1 - libs/components/src/a11y/a11y-cell.directive.ts | 1 - libs/components/src/a11y/a11y-grid.directive.ts | 1 - libs/components/src/a11y/a11y-row.directive.ts | 1 - libs/components/src/a11y/a11y-title.directive.ts | 1 - .../src/async-actions/bit-action.directive.ts | 1 - .../src/async-actions/bit-submit.directive.ts | 1 - .../src/async-actions/form-button.directive.ts | 1 - libs/components/src/avatar/avatar.component.ts | 1 - .../src/badge-list/badge-list.component.ts | 1 - libs/components/src/badge/badge.component.ts | 5 ++--- libs/components/src/banner/banner.component.ts | 1 - .../src/breadcrumbs/breadcrumb.component.ts | 1 - .../src/breadcrumbs/breadcrumbs.component.ts | 1 - libs/components/src/button/button.component.ts | 1 - libs/components/src/callout/callout.component.ts | 1 - libs/components/src/card/card.component.ts | 1 - libs/components/src/checkbox/checkbox.component.ts | 1 - .../src/chip-select/chip-select.component.ts | 1 - .../src/color-password/color-password.component.ts | 1 - .../components/src/container/container.component.ts | 1 - .../src/copy-click/copy-click.directive.spec.ts | 1 - .../src/copy-click/copy-click.directive.ts | 1 - .../src/dialog/dialog/dialog.component.ts | 1 - .../src/dialog/directives/dialog-close.directive.ts | 1 - .../directives/dialog-title-container.directive.ts | 1 - .../simple-configurable-dialog.component.ts | 1 - .../dialog/simple-dialog/simple-dialog.component.ts | 2 -- .../disclosure/disclosure-trigger-for.directive.ts | 1 - .../src/disclosure/disclosure.component.ts | 7 +++---- libs/components/src/drawer/drawer-body.component.ts | 1 - .../components/src/drawer/drawer-close.directive.ts | 1 - .../src/drawer/drawer-header.component.ts | 1 - libs/components/src/drawer/drawer-host.directive.ts | 1 - libs/components/src/drawer/drawer.component.ts | 1 - .../src/form-control/form-control.component.ts | 1 - libs/components/src/form-control/hint.component.ts | 1 - libs/components/src/form-control/label.component.ts | 1 - .../src/form-field/error-summary.component.ts | 1 - libs/components/src/form-field/error.component.ts | 1 - .../src/form-field/form-field.component.ts | 1 - .../form-field/password-input-toggle.directive.ts | 1 - libs/components/src/form-field/prefix.directive.ts | 1 - libs/components/src/form-field/suffix.directive.ts | 1 - .../src/icon-button/icon-button.component.ts | 1 - libs/components/src/icon/icon.component.ts | 1 - libs/components/src/input/input.directive.ts | 1 - libs/components/src/item/item-action.component.ts | 1 - libs/components/src/item/item-content.component.ts | 1 - libs/components/src/item/item-group.component.ts | 1 - libs/components/src/item/item.component.ts | 1 - libs/components/src/layout/layout.component.ts | 1 - libs/components/src/link/link.directive.ts | 2 -- libs/components/src/menu/menu-divider.component.ts | 1 - libs/components/src/menu/menu-item.directive.ts | 1 - libs/components/src/menu/menu.component.ts | 1 - .../src/multi-select/multi-select.component.ts | 1 - .../src/navigation/nav-divider.component.ts | 1 - .../src/navigation/nav-group.component.ts | 1 - libs/components/src/navigation/nav-group.stories.ts | 1 - .../components/src/navigation/nav-item.component.ts | 1 - .../components/src/navigation/nav-logo.component.ts | 1 - .../components/src/navigation/side-nav.component.ts | 1 - libs/components/src/no-items/no-items.component.ts | 1 - .../src/popover/popover-trigger-for.directive.ts | 1 - libs/components/src/popover/popover.component.ts | 1 - libs/components/src/progress/progress.component.ts | 1 - .../src/radio-button/radio-button.component.ts | 1 - .../src/radio-button/radio-group.component.ts | 1 - .../src/radio-button/radio-input.component.ts | 1 - libs/components/src/search/search.component.ts | 1 - .../src/section/section-header.component.ts | 1 - libs/components/src/section/section.component.ts | 1 - libs/components/src/select/option.component.ts | 1 - libs/components/src/select/select.component.spec.ts | 1 - libs/components/src/select/select.component.ts | 1 - .../dialog-virtual-scroll-block.component.ts | 1 - .../components/kitchen-sink-form.component.ts | 1 - .../components/kitchen-sink-main.component.ts | 2 -- .../components/kitchen-sink-table.component.ts | 1 - .../kitchen-sink-toggle-list.component.ts | 1 - libs/components/src/table/cell.directive.ts | 1 - libs/components/src/table/row.directive.ts | 1 - libs/components/src/table/sortable.component.ts | 1 - libs/components/src/table/table-scroll.component.ts | 2 -- libs/components/src/table/table.component.ts | 2 -- .../src/tabs/shared/tab-header.component.ts | 1 - .../src/tabs/shared/tab-list-container.directive.ts | 1 - .../src/tabs/shared/tab-list-item.directive.ts | 1 - .../src/tabs/tab-group/tab-body.component.ts | 1 - .../src/tabs/tab-group/tab-group.component.ts | 1 - .../src/tabs/tab-group/tab-label.directive.ts | 1 - libs/components/src/tabs/tab-group/tab.component.ts | 1 - .../src/tabs/tab-nav-bar/tab-link.component.ts | 1 - .../src/tabs/tab-nav-bar/tab-nav-bar.component.ts | 1 - .../src/toast/toast-container.component.ts | 1 - libs/components/src/toast/toast.component.ts | 1 - libs/components/src/toast/toastr.component.ts | 1 - .../src/toggle-group/toggle-group.component.ts | 1 - .../components/src/toggle-group/toggle.component.ts | 1 - .../src/typography/typography.directive.ts | 1 - libs/ui/common/src/i18n.pipe.ts | 1 - 129 files changed, 5 insertions(+), 151 deletions(-) diff --git a/apps/browser/src/platform/popup/components/pop-out.component.ts b/apps/browser/src/platform/popup/components/pop-out.component.ts index 255c6f853cd..12e32efb77c 100644 --- a/apps/browser/src/platform/popup/components/pop-out.component.ts +++ b/apps/browser/src/platform/popup/components/pop-out.component.ts @@ -10,7 +10,6 @@ import BrowserPopupUtils from "../browser-popup-utils"; @Component({ selector: "app-pop-out", templateUrl: "pop-out.component.html", - standalone: true, imports: [CommonModule, JslibModule, IconButtonModule], }) export class PopOutComponent implements OnInit { diff --git a/apps/browser/src/platform/popup/header.component.ts b/apps/browser/src/platform/popup/header.component.ts index cba9f20b629..0e6e39ee051 100644 --- a/apps/browser/src/platform/popup/header.component.ts +++ b/apps/browser/src/platform/popup/header.component.ts @@ -11,7 +11,6 @@ import { enableAccountSwitching } from "../flags"; @Component({ selector: "app-header", templateUrl: "header.component.html", - standalone: true, imports: [CommonModule, CurrentAccountComponent], }) export class HeaderComponent { diff --git a/apps/browser/src/platform/popup/layout/popup-back.directive.ts b/apps/browser/src/platform/popup/layout/popup-back.directive.ts index 95f82588640..919cee71ba2 100644 --- a/apps/browser/src/platform/popup/layout/popup-back.directive.ts +++ b/apps/browser/src/platform/popup/layout/popup-back.directive.ts @@ -9,7 +9,6 @@ import { PopupRouterCacheService } from "../view-cache/popup-router-cache.servic /** Navigate the browser popup to the previous page when the component is clicked. */ @Directive({ selector: "[popupBackAction]", - standalone: true, }) export class PopupBackBrowserDirective extends BitActionDirective { constructor( diff --git a/apps/browser/src/platform/popup/layout/popup-footer.component.ts b/apps/browser/src/platform/popup/layout/popup-footer.component.ts index 826a1d1c601..928394b0ad4 100644 --- a/apps/browser/src/platform/popup/layout/popup-footer.component.ts +++ b/apps/browser/src/platform/popup/layout/popup-footer.component.ts @@ -3,7 +3,6 @@ import { Component } from "@angular/core"; @Component({ selector: "popup-footer", templateUrl: "popup-footer.component.html", - standalone: true, imports: [], }) export class PopupFooterComponent {} diff --git a/apps/browser/src/platform/popup/layout/popup-header.component.ts b/apps/browser/src/platform/popup/layout/popup-header.component.ts index 3a590b284fe..b580b84f39b 100644 --- a/apps/browser/src/platform/popup/layout/popup-header.component.ts +++ b/apps/browser/src/platform/popup/layout/popup-header.component.ts @@ -19,7 +19,6 @@ import { PopupPageComponent } from "./popup-page.component"; @Component({ selector: "popup-header", templateUrl: "popup-header.component.html", - standalone: true, imports: [TypographyModule, CommonModule, IconButtonModule, JslibModule, AsyncActionsModule], }) export class PopupHeaderComponent { diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts index 48940f5fa10..aecbaf673dc 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts +++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts @@ -36,7 +36,6 @@ import { PopupTabNavigationComponent } from "./popup-tab-navigation.component"; <ng-content></ng-content> </div> `, - standalone: true, }) class ExtensionContainerComponent {} @@ -71,7 +70,6 @@ class ExtensionContainerComponent {} </bit-item-group> </bit-section> `, - standalone: true, imports: [CommonModule, ItemModule, BadgeModule, IconButtonModule, SectionComponent], }) class VaultComponent { @@ -86,7 +84,6 @@ class VaultComponent { Add </button> `, - standalone: true, imports: [ButtonModule], }) class MockAddButtonComponent {} @@ -102,7 +99,6 @@ class MockAddButtonComponent {} aria-label="Pop out" ></button> `, - standalone: true, imports: [IconButtonModule], }) class MockPopoutButtonComponent {} @@ -114,7 +110,6 @@ class MockPopoutButtonComponent {} <bit-avatar text="Ash Ketchum" size="small"></bit-avatar> </button> `, - standalone: true, imports: [AvatarModule], }) class MockCurrentAccountComponent {} @@ -122,7 +117,6 @@ class MockCurrentAccountComponent {} @Component({ selector: "mock-search", template: ` <bit-search placeholder="Search"> </bit-search> `, - standalone: true, imports: [SearchModule], }) class MockSearchComponent {} @@ -134,7 +128,6 @@ class MockSearchComponent {} This is an important note about these ciphers </bit-banner> `, - standalone: true, imports: [BannerModule], }) class MockBannerComponent {} @@ -154,7 +147,6 @@ class MockBannerComponent {} <vault-placeholder></vault-placeholder> </popup-page> `, - standalone: true, imports: [ PopupPageComponent, PopupHeaderComponent, @@ -180,7 +172,6 @@ class MockVaultPageComponent {} <vault-placeholder></vault-placeholder> </popup-page> `, - standalone: true, imports: [ PopupPageComponent, PopupHeaderComponent, @@ -205,7 +196,6 @@ class MockVaultPagePoppedComponent {} <div class="tw-text-main">Generator content here</div> </popup-page> `, - standalone: true, imports: [ PopupPageComponent, PopupHeaderComponent, @@ -230,7 +220,6 @@ class MockGeneratorPageComponent {} <div class="tw-text-main">Send content here</div> </popup-page> `, - standalone: true, imports: [ PopupPageComponent, PopupHeaderComponent, @@ -255,7 +244,6 @@ class MockSendPageComponent {} <div class="tw-text-main">Settings content here</div> </popup-page> `, - standalone: true, imports: [ PopupPageComponent, PopupHeaderComponent, @@ -283,7 +271,6 @@ class MockSettingsPageComponent {} </popup-footer> </popup-page> `, - standalone: true, imports: [ PopupPageComponent, PopupHeaderComponent, diff --git a/apps/browser/src/platform/popup/layout/popup-page.component.ts b/apps/browser/src/platform/popup/layout/popup-page.component.ts index ca019c16bd7..12bd000ca55 100644 --- a/apps/browser/src/platform/popup/layout/popup-page.component.ts +++ b/apps/browser/src/platform/popup/layout/popup-page.component.ts @@ -6,7 +6,6 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic @Component({ selector: "popup-page", templateUrl: "popup-page.component.html", - standalone: true, host: { class: "tw-h-full tw-flex tw-flex-col tw-overflow-y-hidden", }, diff --git a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts index 4984d3749a1..8a897e2e21b 100644 --- a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts +++ b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts @@ -17,7 +17,6 @@ export type NavButton = { @Component({ selector: "popup-tab-navigation", templateUrl: "popup-tab-navigation.component.html", - standalone: true, imports: [CommonModule, LinkModule, RouterModule, JslibModule, IconModule], host: { class: "tw-block tw-h-full tw-w-full tw-flex tw-flex-col", diff --git a/apps/browser/src/popup/components/desktop-sync-verification-dialog.component.ts b/apps/browser/src/popup/components/desktop-sync-verification-dialog.component.ts index 5003bbdc936..2ca24da6c75 100644 --- a/apps/browser/src/popup/components/desktop-sync-verification-dialog.component.ts +++ b/apps/browser/src/popup/components/desktop-sync-verification-dialog.component.ts @@ -17,7 +17,6 @@ export type DesktopSyncVerificationDialogParams = { @Component({ templateUrl: "desktop-sync-verification-dialog.component.html", - standalone: true, imports: [JslibModule, ButtonModule, DialogModule], }) export class DesktopSyncVerificationDialogComponent implements OnDestroy, OnInit { diff --git a/apps/desktop/src/app/components/browser-sync-verification-dialog.component.ts b/apps/desktop/src/app/components/browser-sync-verification-dialog.component.ts index 1f456aee4bb..713dc07e803 100644 --- a/apps/desktop/src/app/components/browser-sync-verification-dialog.component.ts +++ b/apps/desktop/src/app/components/browser-sync-verification-dialog.component.ts @@ -9,7 +9,6 @@ export type BrowserSyncVerificationDialogParams = { @Component({ templateUrl: "browser-sync-verification-dialog.component.html", - standalone: true, imports: [JslibModule, ButtonModule, DialogModule], }) export class BrowserSyncVerificationDialogComponent { diff --git a/apps/desktop/src/app/components/user-verification.component.ts b/apps/desktop/src/app/components/user-verification.component.ts index 2a005f636f3..31d38b10183 100644 --- a/apps/desktop/src/app/components/user-verification.component.ts +++ b/apps/desktop/src/app/components/user-verification.component.ts @@ -13,7 +13,6 @@ import { FormFieldModule } from "@bitwarden/components"; */ @Component({ selector: "app-user-verification", - standalone: true, imports: [CommonModule, JslibModule, ReactiveFormsModule, FormFieldModule, FormsModule], templateUrl: "user-verification.component.html", providers: [ diff --git a/apps/desktop/src/app/components/verify-native-messaging-dialog.component.ts b/apps/desktop/src/app/components/verify-native-messaging-dialog.component.ts index 36c8d9b173a..72284d007b6 100644 --- a/apps/desktop/src/app/components/verify-native-messaging-dialog.component.ts +++ b/apps/desktop/src/app/components/verify-native-messaging-dialog.component.ts @@ -9,7 +9,6 @@ export type VerifyNativeMessagingDialogData = { @Component({ templateUrl: "verify-native-messaging-dialog.component.html", - standalone: true, imports: [JslibModule, ButtonModule, DialogModule], }) export class VerifyNativeMessagingDialogComponent { diff --git a/apps/desktop/src/app/layout/nav.component.ts b/apps/desktop/src/app/layout/nav.component.ts index dbc399c051d..bcc2b57fb17 100644 --- a/apps/desktop/src/app/layout/nav.component.ts +++ b/apps/desktop/src/app/layout/nav.component.ts @@ -7,7 +7,6 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic @Component({ selector: "app-nav", templateUrl: "nav.component.html", - standalone: true, imports: [CommonModule, RouterLink, RouterLinkActive], }) export class NavComponent { diff --git a/apps/desktop/src/platform/components/approve-ssh-request.ts b/apps/desktop/src/platform/components/approve-ssh-request.ts index 515fd94ecd6..8cd63e0b1ac 100644 --- a/apps/desktop/src/platform/components/approve-ssh-request.ts +++ b/apps/desktop/src/platform/components/approve-ssh-request.ts @@ -24,7 +24,6 @@ export interface ApproveSshRequestParams { @Component({ selector: "app-approve-ssh-request", templateUrl: "approve-ssh-request.html", - standalone: true, imports: [ DialogModule, CommonModule, diff --git a/apps/web/src/app/components/dynamic-avatar.component.ts b/apps/web/src/app/components/dynamic-avatar.component.ts index 4381524de66..8cd73862151 100644 --- a/apps/web/src/app/components/dynamic-avatar.component.ts +++ b/apps/web/src/app/components/dynamic-avatar.component.ts @@ -10,7 +10,6 @@ import { SharedModule } from "../shared"; type SizeTypes = "xlarge" | "large" | "default" | "small" | "xsmall"; @Component({ selector: "dynamic-avatar", - standalone: true, imports: [SharedModule], template: `<span [title]="title"> <bit-avatar diff --git a/apps/web/src/app/components/environment-selector/environment-selector.component.ts b/apps/web/src/app/components/environment-selector/environment-selector.component.ts index ba0f5097b5b..37e5ae0c3d8 100644 --- a/apps/web/src/app/components/environment-selector/environment-selector.component.ts +++ b/apps/web/src/app/components/environment-selector/environment-selector.component.ts @@ -15,7 +15,6 @@ import { SharedModule } from "../../shared"; @Component({ selector: "environment-selector", templateUrl: "environment-selector.component.html", - standalone: true, imports: [SharedModule], }) export class EnvironmentSelectorComponent implements OnInit { diff --git a/apps/web/src/app/layouts/frontend-layout.component.ts b/apps/web/src/app/layouts/frontend-layout.component.ts index 5ccb39b1dc9..a91fc92df61 100644 --- a/apps/web/src/app/layouts/frontend-layout.component.ts +++ b/apps/web/src/app/layouts/frontend-layout.component.ts @@ -10,7 +10,6 @@ import { SharedModule } from "../shared"; @Component({ selector: "app-frontend-layout", templateUrl: "frontend-layout.component.html", - standalone: true, imports: [SharedModule, EnvironmentSelectorComponent], }) export class FrontendLayoutComponent implements OnInit, OnDestroy { diff --git a/apps/web/src/app/layouts/header/web-header.stories.ts b/apps/web/src/app/layouts/header/web-header.stories.ts index d3dc9604710..9715dbf8cd3 100644 --- a/apps/web/src/app/layouts/header/web-header.stories.ts +++ b/apps/web/src/app/layouts/header/web-header.stories.ts @@ -56,7 +56,6 @@ class MockProductSwitcher {} @Component({ selector: "dynamic-avatar", template: `<bit-avatar [text]="name$ | async"></bit-avatar>`, - standalone: true, imports: [CommonModule, AvatarModule], }) class MockDynamicAvatar implements Partial<DynamicAvatarComponent> { diff --git a/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts b/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts index d64e1b817c1..4f2707cd1b2 100644 --- a/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts +++ b/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts @@ -17,7 +17,6 @@ import { TrialFlowService } from "./../../billing/services/trial-flow.service"; @Component({ selector: "org-switcher", templateUrl: "org-switcher.component.html", - standalone: true, imports: [CommonModule, JslibModule, NavigationModule], }) export class OrgSwitcherComponent { diff --git a/apps/web/src/app/layouts/toggle-width.component.ts b/apps/web/src/app/layouts/toggle-width.component.ts index 36f33c6accf..411fc73b175 100644 --- a/apps/web/src/app/layouts/toggle-width.component.ts +++ b/apps/web/src/app/layouts/toggle-width.component.ts @@ -12,7 +12,6 @@ import { NavigationModule } from "@bitwarden/components"; *ngIf="isDev" (click)="toggleWidth()" ></bit-nav-item>`, - standalone: true, imports: [CommonModule, NavigationModule], }) export class ToggleWidthComponent { diff --git a/apps/web/src/app/layouts/user-layout.component.ts b/apps/web/src/app/layouts/user-layout.component.ts index e859993af32..cd07d625281 100644 --- a/apps/web/src/app/layouts/user-layout.component.ts +++ b/apps/web/src/app/layouts/user-layout.component.ts @@ -19,7 +19,6 @@ import { WebLayoutModule } from "./web-layout.module"; @Component({ selector: "app-user-layout", templateUrl: "user-layout.component.html", - standalone: true, imports: [ CommonModule, RouterModule, diff --git a/apps/web/src/app/layouts/web-layout.component.ts b/apps/web/src/app/layouts/web-layout.component.ts index aa4de4cfee5..2d2635fd296 100644 --- a/apps/web/src/app/layouts/web-layout.component.ts +++ b/apps/web/src/app/layouts/web-layout.component.ts @@ -8,7 +8,6 @@ import { ProductSwitcherModule } from "./product-switcher/product-switcher.modul @Component({ selector: "app-layout", templateUrl: "web-layout.component.html", - standalone: true, imports: [CommonModule, LayoutComponent, ProductSwitcherModule], }) export class WebLayoutComponent { diff --git a/apps/web/src/app/layouts/web-side-nav.component.ts b/apps/web/src/app/layouts/web-side-nav.component.ts index 28b04e87461..364b3bedecc 100644 --- a/apps/web/src/app/layouts/web-side-nav.component.ts +++ b/apps/web/src/app/layouts/web-side-nav.component.ts @@ -9,7 +9,6 @@ import { ToggleWidthComponent } from "./toggle-width.component"; @Component({ selector: "app-side-nav", templateUrl: "web-side-nav.component.html", - standalone: true, imports: [CommonModule, NavigationModule, ProductSwitcherModule, ToggleWidthComponent], }) export class WebSideNavComponent { diff --git a/apps/web/src/app/settings/domain-rules.component.ts b/apps/web/src/app/settings/domain-rules.component.ts index 7656222cfd2..6c4cb13d5fa 100644 --- a/apps/web/src/app/settings/domain-rules.component.ts +++ b/apps/web/src/app/settings/domain-rules.component.ts @@ -15,7 +15,6 @@ import { SharedModule } from "../shared"; @Component({ selector: "app-domain-rules", templateUrl: "domain-rules.component.html", - standalone: true, imports: [SharedModule, HeaderModule], }) export class DomainRulesComponent implements OnInit { diff --git a/apps/web/src/app/settings/preferences.component.ts b/apps/web/src/app/settings/preferences.component.ts index c9efd059271..e6cc35903a7 100644 --- a/apps/web/src/app/settings/preferences.component.ts +++ b/apps/web/src/app/settings/preferences.component.ts @@ -41,7 +41,6 @@ import { SharedModule } from "../shared"; @Component({ selector: "app-preferences", templateUrl: "preferences.component.html", - standalone: true, imports: [SharedModule, HeaderModule, VaultTimeoutInputComponent], }) export class PreferencesComponent implements OnInit, OnDestroy { diff --git a/apps/web/src/app/shared/components/account-fingerprint/account-fingerprint.component.ts b/apps/web/src/app/shared/components/account-fingerprint/account-fingerprint.component.ts index 0b79ad6fbb9..256c8d6af34 100644 --- a/apps/web/src/app/shared/components/account-fingerprint/account-fingerprint.component.ts +++ b/apps/web/src/app/shared/components/account-fingerprint/account-fingerprint.component.ts @@ -9,7 +9,6 @@ import { SharedModule } from "../../shared.module"; @Component({ selector: "app-account-fingerprint", templateUrl: "account-fingerprint.component.html", - standalone: true, imports: [SharedModule], }) export class AccountFingerprintComponent implements OnInit { diff --git a/libs/angular/src/directives/text-drag.directive.ts b/libs/angular/src/directives/text-drag.directive.ts index 443fbdac157..6202c552a87 100644 --- a/libs/angular/src/directives/text-drag.directive.ts +++ b/libs/angular/src/directives/text-drag.directive.ts @@ -2,7 +2,6 @@ import { Directive, HostListener, Input } from "@angular/core"; @Directive({ selector: "[appTextDrag]", - standalone: true, host: { draggable: "true", class: "tw-cursor-move", diff --git a/libs/angular/src/pipes/pluralize.pipe.ts b/libs/angular/src/pipes/pluralize.pipe.ts index cc3aa3e0aa7..882cc637bf1 100644 --- a/libs/angular/src/pipes/pluralize.pipe.ts +++ b/libs/angular/src/pipes/pluralize.pipe.ts @@ -2,7 +2,6 @@ import { Pipe, PipeTransform } from "@angular/core"; @Pipe({ name: "pluralize", - standalone: true, }) export class PluralizePipe implements PipeTransform { transform(count: number, singular: string, plural: string): string { diff --git a/libs/components/src/a11y/a11y-cell.directive.ts b/libs/components/src/a11y/a11y-cell.directive.ts index c9a8fdda255..3a2d5c4f6b2 100644 --- a/libs/components/src/a11y/a11y-cell.directive.ts +++ b/libs/components/src/a11y/a11y-cell.directive.ts @@ -6,7 +6,6 @@ import { FocusableElement } from "../shared/focusable-element"; @Directive({ selector: "bitA11yCell", - standalone: true, providers: [{ provide: FocusableElement, useExisting: A11yCellDirective }], }) export class A11yCellDirective implements FocusableElement { diff --git a/libs/components/src/a11y/a11y-grid.directive.ts b/libs/components/src/a11y/a11y-grid.directive.ts index ef7ba68b65c..c061464239e 100644 --- a/libs/components/src/a11y/a11y-grid.directive.ts +++ b/libs/components/src/a11y/a11y-grid.directive.ts @@ -15,7 +15,6 @@ import { A11yRowDirective } from "./a11y-row.directive"; @Directive({ selector: "bitA11yGrid", - standalone: true, }) export class A11yGridDirective implements AfterViewInit { @HostBinding("attr.role") diff --git a/libs/components/src/a11y/a11y-row.directive.ts b/libs/components/src/a11y/a11y-row.directive.ts index 7e0431d17e2..f7588dc0053 100644 --- a/libs/components/src/a11y/a11y-row.directive.ts +++ b/libs/components/src/a11y/a11y-row.directive.ts @@ -13,7 +13,6 @@ import { A11yCellDirective } from "./a11y-cell.directive"; @Directive({ selector: "bitA11yRow", - standalone: true, }) export class A11yRowDirective implements AfterViewInit { @HostBinding("attr.role") diff --git a/libs/components/src/a11y/a11y-title.directive.ts b/libs/components/src/a11y/a11y-title.directive.ts index c3833f42ed2..f5f016b93c0 100644 --- a/libs/components/src/a11y/a11y-title.directive.ts +++ b/libs/components/src/a11y/a11y-title.directive.ts @@ -4,7 +4,6 @@ import { Directive, ElementRef, Input, OnInit, Renderer2 } from "@angular/core"; @Directive({ selector: "[appA11yTitle]", - standalone: true, }) export class A11yTitleDirective implements OnInit { @Input() set appA11yTitle(title: string) { diff --git a/libs/components/src/async-actions/bit-action.directive.ts b/libs/components/src/async-actions/bit-action.directive.ts index ac50082852a..46132803475 100644 --- a/libs/components/src/async-actions/bit-action.directive.ts +++ b/libs/components/src/async-actions/bit-action.directive.ts @@ -15,7 +15,6 @@ import { FunctionReturningAwaitable, functionToObservable } from "../utils/funct */ @Directive({ selector: "[bitAction]", - standalone: true, }) export class BitActionDirective implements OnDestroy { private destroy$ = new Subject<void>(); diff --git a/libs/components/src/async-actions/bit-submit.directive.ts b/libs/components/src/async-actions/bit-submit.directive.ts index a38e76aaca6..838d78af8b2 100644 --- a/libs/components/src/async-actions/bit-submit.directive.ts +++ b/libs/components/src/async-actions/bit-submit.directive.ts @@ -14,7 +14,6 @@ import { FunctionReturningAwaitable, functionToObservable } from "../utils/funct */ @Directive({ selector: "[formGroup][bitSubmit]", - standalone: true, }) export class BitSubmitDirective implements OnInit, OnDestroy { private destroy$ = new Subject<void>(); diff --git a/libs/components/src/async-actions/form-button.directive.ts b/libs/components/src/async-actions/form-button.directive.ts index 1c2855f32e7..95a133403bf 100644 --- a/libs/components/src/async-actions/form-button.directive.ts +++ b/libs/components/src/async-actions/form-button.directive.ts @@ -25,7 +25,6 @@ import { BitSubmitDirective } from "./bit-submit.directive"; */ @Directive({ selector: "button[bitFormButton]", - standalone: true, }) export class BitFormButtonDirective implements OnDestroy { private destroy$ = new Subject<void>(); diff --git a/libs/components/src/avatar/avatar.component.ts b/libs/components/src/avatar/avatar.component.ts index c66bba1c462..8ccd639a41d 100644 --- a/libs/components/src/avatar/avatar.component.ts +++ b/libs/components/src/avatar/avatar.component.ts @@ -27,7 +27,6 @@ const SizeClasses: Record<SizeTypes, string[]> = { template: `@if (src) { <img [src]="src" title="{{ title || text }}" [ngClass]="classList" /> }`, - standalone: true, imports: [NgClass], }) export class AvatarComponent implements OnChanges { diff --git a/libs/components/src/badge-list/badge-list.component.ts b/libs/components/src/badge-list/badge-list.component.ts index 86e9a84cb77..7b719a4ec86 100644 --- a/libs/components/src/badge-list/badge-list.component.ts +++ b/libs/components/src/badge-list/badge-list.component.ts @@ -10,7 +10,6 @@ import { BadgeModule, BadgeVariant } from "../badge"; @Component({ selector: "bit-badge-list", templateUrl: "badge-list.component.html", - standalone: true, imports: [BadgeModule, I18nPipe], }) export class BadgeListComponent implements OnChanges { diff --git a/libs/components/src/badge/badge.component.ts b/libs/components/src/badge/badge.component.ts index 3612827eff2..e2cbb4f1ceb 100644 --- a/libs/components/src/badge/badge.component.ts +++ b/libs/components/src/badge/badge.component.ts @@ -51,16 +51,15 @@ const hoverStyles: Record<BadgeVariant, string[]> = { * The Badge directive can be used on a `<span>` (non clickable events), or an `<a>` or `<button>` tag * > `NOTE:` The Focus and Hover states only apply to badges used for interactive events. - * + * * > `NOTE:` The `disabled` state only applies to buttons. - * + * */ @Component({ selector: "span[bitBadge], a[bitBadge], button[bitBadge]", providers: [{ provide: FocusableElement, useExisting: BadgeComponent }], imports: [CommonModule], templateUrl: "badge.component.html", - standalone: true, }) export class BadgeComponent implements FocusableElement { @HostBinding("class") get classList() { diff --git a/libs/components/src/banner/banner.component.ts b/libs/components/src/banner/banner.component.ts index a6719f25989..02d55230ce2 100644 --- a/libs/components/src/banner/banner.component.ts +++ b/libs/components/src/banner/banner.component.ts @@ -28,7 +28,6 @@ const defaultIcon: Record<BannerType, string> = { @Component({ selector: "bit-banner", templateUrl: "./banner.component.html", - standalone: true, imports: [CommonModule, IconButtonModule, I18nPipe], }) export class BannerComponent implements OnInit { diff --git a/libs/components/src/breadcrumbs/breadcrumb.component.ts b/libs/components/src/breadcrumbs/breadcrumb.component.ts index 53c46a9b24a..d466ef61c1a 100644 --- a/libs/components/src/breadcrumbs/breadcrumb.component.ts +++ b/libs/components/src/breadcrumbs/breadcrumb.component.ts @@ -7,7 +7,6 @@ import { QueryParamsHandling } from "@angular/router"; @Component({ selector: "bit-breadcrumb", templateUrl: "./breadcrumb.component.html", - standalone: true, }) export class BreadcrumbComponent { @Input() diff --git a/libs/components/src/breadcrumbs/breadcrumbs.component.ts b/libs/components/src/breadcrumbs/breadcrumbs.component.ts index 24265212969..1ff575d5070 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.component.ts +++ b/libs/components/src/breadcrumbs/breadcrumbs.component.ts @@ -16,7 +16,6 @@ import { BreadcrumbComponent } from "./breadcrumb.component"; @Component({ selector: "bit-breadcrumbs", templateUrl: "./breadcrumbs.component.html", - standalone: true, imports: [CommonModule, LinkModule, RouterModule, IconButtonModule, MenuModule], }) export class BreadcrumbsComponent { diff --git a/libs/components/src/button/button.component.ts b/libs/components/src/button/button.component.ts index 19618938c42..002b2a9d915 100644 --- a/libs/components/src/button/button.component.ts +++ b/libs/components/src/button/button.component.ts @@ -52,7 +52,6 @@ const buttonStyles: Record<ButtonType, string[]> = { selector: "button[bitButton], a[bitButton]", templateUrl: "button.component.html", providers: [{ provide: ButtonLikeAbstraction, useExisting: ButtonComponent }], - standalone: true, imports: [NgClass], host: { "[attr.disabled]": "disabledAttr()", diff --git a/libs/components/src/callout/callout.component.ts b/libs/components/src/callout/callout.component.ts index e1bd7f1a596..d5dfa04a809 100644 --- a/libs/components/src/callout/callout.component.ts +++ b/libs/components/src/callout/callout.component.ts @@ -32,7 +32,6 @@ let nextId = 0; @Component({ selector: "bit-callout", templateUrl: "callout.component.html", - standalone: true, imports: [SharedModule, TypographyModule], }) export class CalloutComponent implements OnInit { diff --git a/libs/components/src/card/card.component.ts b/libs/components/src/card/card.component.ts index fdb02f280da..d7e36d1ea9e 100644 --- a/libs/components/src/card/card.component.ts +++ b/libs/components/src/card/card.component.ts @@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, Component } from "@angular/core"; @Component({ selector: "bit-card", - standalone: true, template: `<ng-content></ng-content>`, changeDetection: ChangeDetectionStrategy.OnPush, host: { diff --git a/libs/components/src/checkbox/checkbox.component.ts b/libs/components/src/checkbox/checkbox.component.ts index 05993ee4e7a..079ede287cc 100644 --- a/libs/components/src/checkbox/checkbox.component.ts +++ b/libs/components/src/checkbox/checkbox.component.ts @@ -9,7 +9,6 @@ import { BitFormControlAbstraction } from "../form-control"; selector: "input[type=checkbox][bitCheckbox]", template: "", providers: [{ provide: BitFormControlAbstraction, useExisting: CheckboxComponent }], - standalone: true, }) export class CheckboxComponent implements BitFormControlAbstraction { @HostBinding("class") diff --git a/libs/components/src/chip-select/chip-select.component.ts b/libs/components/src/chip-select/chip-select.component.ts index 270249ade0c..2eede684688 100644 --- a/libs/components/src/chip-select/chip-select.component.ts +++ b/libs/components/src/chip-select/chip-select.component.ts @@ -39,7 +39,6 @@ export type ChipSelectOption<T> = Option<T> & { @Component({ selector: "bit-chip-select", templateUrl: "chip-select.component.html", - standalone: true, imports: [SharedModule, ButtonModule, IconButtonModule, MenuModule, TypographyModule], providers: [ { diff --git a/libs/components/src/color-password/color-password.component.ts b/libs/components/src/color-password/color-password.component.ts index a6cd58044a3..fb6f6568101 100644 --- a/libs/components/src/color-password/color-password.component.ts +++ b/libs/components/src/color-password/color-password.component.ts @@ -25,7 +25,6 @@ enum CharacterType { } </span> }`, - standalone: true, }) export class ColorPasswordComponent { password = input<string>(""); diff --git a/libs/components/src/container/container.component.ts b/libs/components/src/container/container.component.ts index 1bcdb8f459b..2f9e15c06b8 100644 --- a/libs/components/src/container/container.component.ts +++ b/libs/components/src/container/container.component.ts @@ -6,6 +6,5 @@ import { Component } from "@angular/core"; @Component({ selector: "bit-container", templateUrl: "container.component.html", - standalone: true, }) export class ContainerComponent {} diff --git a/libs/components/src/copy-click/copy-click.directive.spec.ts b/libs/components/src/copy-click/copy-click.directive.spec.ts index eab616b141e..38f8ccb43cb 100644 --- a/libs/components/src/copy-click/copy-click.directive.spec.ts +++ b/libs/components/src/copy-click/copy-click.directive.spec.ts @@ -21,7 +21,6 @@ import { CopyClickDirective } from "./copy-click.directive"; #toastWithLabel ></button> `, - standalone: true, imports: [CopyClickDirective], }) class TestCopyClickComponent { diff --git a/libs/components/src/copy-click/copy-click.directive.ts b/libs/components/src/copy-click/copy-click.directive.ts index f91366360c5..1dfaf4387dc 100644 --- a/libs/components/src/copy-click/copy-click.directive.ts +++ b/libs/components/src/copy-click/copy-click.directive.ts @@ -9,7 +9,6 @@ import { ToastService, ToastVariant } from "../"; @Directive({ selector: "[appCopyClick]", - standalone: true, }) export class CopyClickDirective { private _showToast = false; diff --git a/libs/components/src/dialog/dialog/dialog.component.ts b/libs/components/src/dialog/dialog/dialog.component.ts index 504dbd3a1ea..de521b62909 100644 --- a/libs/components/src/dialog/dialog/dialog.component.ts +++ b/libs/components/src/dialog/dialog/dialog.component.ts @@ -16,7 +16,6 @@ import { DialogTitleContainerDirective } from "../directives/dialog-title-contai selector: "bit-dialog", templateUrl: "./dialog.component.html", animations: [fadeIn], - standalone: true, imports: [ CommonModule, DialogTitleContainerDirective, diff --git a/libs/components/src/dialog/directives/dialog-close.directive.ts b/libs/components/src/dialog/directives/dialog-close.directive.ts index 5e5fda3e014..5e44ced7c21 100644 --- a/libs/components/src/dialog/directives/dialog-close.directive.ts +++ b/libs/components/src/dialog/directives/dialog-close.directive.ts @@ -3,7 +3,6 @@ import { Directive, HostBinding, HostListener, Input, Optional } from "@angular/ @Directive({ selector: "[bitDialogClose]", - standalone: true, }) export class DialogCloseDirective { @Input("bitDialogClose") dialogResult: any; diff --git a/libs/components/src/dialog/directives/dialog-title-container.directive.ts b/libs/components/src/dialog/directives/dialog-title-container.directive.ts index cf46396967b..e17487f2780 100644 --- a/libs/components/src/dialog/directives/dialog-title-container.directive.ts +++ b/libs/components/src/dialog/directives/dialog-title-container.directive.ts @@ -6,7 +6,6 @@ let nextId = 0; @Directive({ selector: "[bitDialogTitleContainer]", - standalone: true, }) export class DialogTitleContainerDirective implements OnInit { @HostBinding("id") id = `bit-dialog-title-${nextId++}`; diff --git a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts index 00026209183..f849fe81df6 100644 --- a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts +++ b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts @@ -30,7 +30,6 @@ const DEFAULT_COLOR: Record<SimpleDialogType, string> = { @Component({ templateUrl: "./simple-configurable-dialog.component.html", - standalone: true, imports: [ ReactiveFormsModule, BitSubmitDirective, diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts b/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts index db7023b5b86..85f1bed8cf5 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts @@ -6,7 +6,6 @@ import { DialogTitleContainerDirective } from "../directives/dialog-title-contai @Directive({ selector: "[bitDialogIcon]", - standalone: true, }) export class IconDirective {} @@ -14,7 +13,6 @@ export class IconDirective {} selector: "bit-simple-dialog", templateUrl: "./simple-dialog.component.html", animations: [fadeIn], - standalone: true, imports: [DialogTitleContainerDirective, TypographyDirective], }) export class SimpleDialogComponent { diff --git a/libs/components/src/disclosure/disclosure-trigger-for.directive.ts b/libs/components/src/disclosure/disclosure-trigger-for.directive.ts index 6db26410bea..bf7bdb409ec 100644 --- a/libs/components/src/disclosure/disclosure-trigger-for.directive.ts +++ b/libs/components/src/disclosure/disclosure-trigger-for.directive.ts @@ -7,7 +7,6 @@ import { DisclosureComponent } from "./disclosure.component"; @Directive({ selector: "[bitDisclosureTriggerFor]", exportAs: "disclosureTriggerFor", - standalone: true, }) export class DisclosureTriggerForDirective { /** diff --git a/libs/components/src/disclosure/disclosure.component.ts b/libs/components/src/disclosure/disclosure.component.ts index c18a2e31ea6..58e425e9206 100644 --- a/libs/components/src/disclosure/disclosure.component.ts +++ b/libs/components/src/disclosure/disclosure.component.ts @@ -21,9 +21,9 @@ let nextId = 0; * 3. Set a template reference on the `bit-disclosure` * 4. Use the `bitDisclosureTriggerFor` directive on the trigger component, and pass it the `bit-disclosure` template reference * 5. Set the `open` property on the `bit-disclosure` to init the disclosure as either currently expanded or currently collapsed. The disclosure will default to `false`, meaning it defaults to being hidden. - * + * * @example - * + * * ```html * <button * type="button" @@ -33,11 +33,10 @@ let nextId = 0; * ></button> * <bit-disclosure #disclosureRef open>click button to hide this content</bit-disclosure> * ``` - * + * */ @Component({ selector: "bit-disclosure", - standalone: true, template: `<ng-content></ng-content>`, }) export class DisclosureComponent { diff --git a/libs/components/src/drawer/drawer-body.component.ts b/libs/components/src/drawer/drawer-body.component.ts index 9bd2adcffbc..d491425f68a 100644 --- a/libs/components/src/drawer/drawer-body.component.ts +++ b/libs/components/src/drawer/drawer-body.component.ts @@ -8,7 +8,6 @@ import { map } from "rxjs"; */ @Component({ selector: "bit-drawer-body", - standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [], host: { diff --git a/libs/components/src/drawer/drawer-close.directive.ts b/libs/components/src/drawer/drawer-close.directive.ts index bf56dd8b71f..f105e21ea62 100644 --- a/libs/components/src/drawer/drawer-close.directive.ts +++ b/libs/components/src/drawer/drawer-close.directive.ts @@ -15,7 +15,6 @@ import { DrawerComponent } from "./drawer.component"; **/ @Directive({ selector: "button[bitDrawerClose]", - standalone: true, host: { "(click)": "onClick()", }, diff --git a/libs/components/src/drawer/drawer-header.component.ts b/libs/components/src/drawer/drawer-header.component.ts index c78a9020200..36addcd2bea 100644 --- a/libs/components/src/drawer/drawer-header.component.ts +++ b/libs/components/src/drawer/drawer-header.component.ts @@ -13,7 +13,6 @@ import { DrawerCloseDirective } from "./drawer-close.directive"; **/ @Component({ selector: "bit-drawer-header", - standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, DrawerCloseDirective, TypographyModule, IconButtonModule, I18nPipe], templateUrl: "drawer-header.component.html", diff --git a/libs/components/src/drawer/drawer-host.directive.ts b/libs/components/src/drawer/drawer-host.directive.ts index f5e3e56b099..64eea6a9c06 100644 --- a/libs/components/src/drawer/drawer-host.directive.ts +++ b/libs/components/src/drawer/drawer-host.directive.ts @@ -8,7 +8,6 @@ import { Directive, signal } from "@angular/core"; */ @Directive({ selector: "[bitDrawerHost]", - standalone: true, }) export class DrawerHostDirective { private _portal = signal<Portal<unknown> | undefined>(undefined); diff --git a/libs/components/src/drawer/drawer.component.ts b/libs/components/src/drawer/drawer.component.ts index ccabb6f0b6e..387bd63c918 100644 --- a/libs/components/src/drawer/drawer.component.ts +++ b/libs/components/src/drawer/drawer.component.ts @@ -19,7 +19,6 @@ import { DrawerHostDirective } from "./drawer-host.directive"; */ @Component({ selector: "bit-drawer", - standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, PortalModule], templateUrl: "drawer.component.html", diff --git a/libs/components/src/form-control/form-control.component.ts b/libs/components/src/form-control/form-control.component.ts index c59536e2410..0e2fa393f80 100644 --- a/libs/components/src/form-control/form-control.component.ts +++ b/libs/components/src/form-control/form-control.component.ts @@ -14,7 +14,6 @@ import { BitFormControlAbstraction } from "./form-control.abstraction"; @Component({ selector: "bit-form-control", templateUrl: "form-control.component.html", - standalone: true, imports: [NgClass, TypographyDirective, I18nPipe], }) export class FormControlComponent { diff --git a/libs/components/src/form-control/hint.component.ts b/libs/components/src/form-control/hint.component.ts index 4fee0d4560f..c1f21bf2545 100644 --- a/libs/components/src/form-control/hint.component.ts +++ b/libs/components/src/form-control/hint.component.ts @@ -8,7 +8,6 @@ let nextId = 0; host: { class: "tw-text-muted tw-font-normal tw-inline-block tw-mt-1 tw-text-xs", }, - standalone: true, }) export class BitHintComponent { @HostBinding() id = `bit-hint-${nextId++}`; diff --git a/libs/components/src/form-control/label.component.ts b/libs/components/src/form-control/label.component.ts index e0c4ebf466b..d5028715bd5 100644 --- a/libs/components/src/form-control/label.component.ts +++ b/libs/components/src/form-control/label.component.ts @@ -10,7 +10,6 @@ let nextId = 0; @Component({ selector: "bit-label", - standalone: true, templateUrl: "label.component.html", imports: [CommonModule], }) diff --git a/libs/components/src/form-field/error-summary.component.ts b/libs/components/src/form-field/error-summary.component.ts index 1709c3078fa..c57819a1fca 100644 --- a/libs/components/src/form-field/error-summary.component.ts +++ b/libs/components/src/form-field/error-summary.component.ts @@ -15,7 +15,6 @@ import { I18nPipe } from "@bitwarden/ui-common"; class: "tw-block tw-text-danger tw-mt-2", "aria-live": "assertive", }, - standalone: true, imports: [I18nPipe], }) export class BitErrorSummary { diff --git a/libs/components/src/form-field/error.component.ts b/libs/components/src/form-field/error.component.ts index 27adbf7d313..a0f7906b366 100644 --- a/libs/components/src/form-field/error.component.ts +++ b/libs/components/src/form-field/error.component.ts @@ -14,7 +14,6 @@ let nextId = 0; class: "tw-block tw-mt-1 tw-text-danger tw-text-xs", "aria-live": "assertive", }, - standalone: true, }) export class BitErrorComponent { @HostBinding() id = `bit-error-${nextId++}`; diff --git a/libs/components/src/form-field/form-field.component.ts b/libs/components/src/form-field/form-field.component.ts index e810aaec8cb..954297a8aa4 100644 --- a/libs/components/src/form-field/form-field.component.ts +++ b/libs/components/src/form-field/form-field.component.ts @@ -26,7 +26,6 @@ import { BitFormFieldControl } from "./form-field-control"; @Component({ selector: "bit-form-field", templateUrl: "./form-field.component.html", - standalone: true, imports: [CommonModule, BitErrorComponent, I18nPipe], }) export class BitFormFieldComponent implements AfterContentChecked { diff --git a/libs/components/src/form-field/password-input-toggle.directive.ts b/libs/components/src/form-field/password-input-toggle.directive.ts index 933722db5b4..a696a88c468 100644 --- a/libs/components/src/form-field/password-input-toggle.directive.ts +++ b/libs/components/src/form-field/password-input-toggle.directive.ts @@ -18,7 +18,6 @@ import { BitFormFieldComponent } from "./form-field.component"; @Directive({ selector: "[bitPasswordInputToggle]", - standalone: true, }) export class BitPasswordInputToggleDirective implements AfterContentInit, OnChanges { /** diff --git a/libs/components/src/form-field/prefix.directive.ts b/libs/components/src/form-field/prefix.directive.ts index b44e90cbaad..34fcbf85233 100644 --- a/libs/components/src/form-field/prefix.directive.ts +++ b/libs/components/src/form-field/prefix.directive.ts @@ -4,7 +4,6 @@ import { BitIconButtonComponent } from "../icon-button/icon-button.component"; @Directive({ selector: "[bitPrefix]", - standalone: true, }) export class BitPrefixDirective implements OnInit { @HostBinding("class") @Input() get classList() { diff --git a/libs/components/src/form-field/suffix.directive.ts b/libs/components/src/form-field/suffix.directive.ts index baf1afce763..28736ce78a9 100644 --- a/libs/components/src/form-field/suffix.directive.ts +++ b/libs/components/src/form-field/suffix.directive.ts @@ -4,7 +4,6 @@ import { BitIconButtonComponent } from "../icon-button/icon-button.component"; @Directive({ selector: "[bitSuffix]", - standalone: true, }) export class BitSuffixDirective implements OnInit { @HostBinding("class") @Input() get classList() { diff --git a/libs/components/src/icon-button/icon-button.component.ts b/libs/components/src/icon-button/icon-button.component.ts index 573708b1e40..70331b84db8 100644 --- a/libs/components/src/icon-button/icon-button.component.ts +++ b/libs/components/src/icon-button/icon-button.component.ts @@ -161,7 +161,6 @@ const sizes: Record<IconButtonSize, string[]> = { { provide: ButtonLikeAbstraction, useExisting: BitIconButtonComponent }, { provide: FocusableElement, useExisting: BitIconButtonComponent }, ], - standalone: true, imports: [NgClass], host: { "[attr.disabled]": "disabledAttr()", diff --git a/libs/components/src/icon/icon.component.ts b/libs/components/src/icon/icon.component.ts index 08fa25956d0..5eae2c1d501 100644 --- a/libs/components/src/icon/icon.component.ts +++ b/libs/components/src/icon/icon.component.ts @@ -11,7 +11,6 @@ import { Icon, isIcon } from "./icon"; "[innerHtml]": "innerHtml", }, template: ``, - standalone: true, }) export class BitIconComponent { innerHtml: SafeHtml | null = null; diff --git a/libs/components/src/input/input.directive.ts b/libs/components/src/input/input.directive.ts index f6c6c3d542e..4a6a03295d4 100644 --- a/libs/components/src/input/input.directive.ts +++ b/libs/components/src/input/input.directive.ts @@ -30,7 +30,6 @@ export function inputBorderClasses(error: boolean) { @Directive({ selector: "input[bitInput], select[bitInput], textarea[bitInput]", providers: [{ provide: BitFormFieldControl, useExisting: BitInputDirective }], - standalone: true, }) export class BitInputDirective implements BitFormFieldControl { @HostBinding("class") @Input() get classList() { diff --git a/libs/components/src/item/item-action.component.ts b/libs/components/src/item/item-action.component.ts index a6ee3a34e6d..d169ee7c00b 100644 --- a/libs/components/src/item/item-action.component.ts +++ b/libs/components/src/item/item-action.component.ts @@ -4,7 +4,6 @@ import { A11yCellDirective } from "../a11y/a11y-cell.directive"; @Component({ selector: "bit-item-action", - standalone: true, imports: [], template: `<ng-content></ng-content>`, providers: [{ provide: A11yCellDirective, useExisting: ItemActionComponent }], diff --git a/libs/components/src/item/item-content.component.ts b/libs/components/src/item/item-content.component.ts index 76fa3996210..0f828de33b4 100644 --- a/libs/components/src/item/item-content.component.ts +++ b/libs/components/src/item/item-content.component.ts @@ -16,7 +16,6 @@ import { TypographyModule } from "../typography"; @Component({ selector: "bit-item-content, [bit-item-content]", - standalone: true, imports: [TypographyModule, NgClass], templateUrl: `item-content.component.html`, host: { diff --git a/libs/components/src/item/item-group.component.ts b/libs/components/src/item/item-group.component.ts index 2a9a8275cc6..6e53d2636be 100644 --- a/libs/components/src/item/item-group.component.ts +++ b/libs/components/src/item/item-group.component.ts @@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, Component } from "@angular/core"; @Component({ selector: "bit-item-group", - standalone: true, imports: [], template: `<ng-content></ng-content>`, changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/libs/components/src/item/item.component.ts b/libs/components/src/item/item.component.ts index 0c45f98139e..1846a53f7a2 100644 --- a/libs/components/src/item/item.component.ts +++ b/libs/components/src/item/item.component.ts @@ -12,7 +12,6 @@ import { ItemActionComponent } from "./item-action.component"; @Component({ selector: "bit-item", - standalone: true, imports: [ItemActionComponent], changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: "item.component.html", diff --git a/libs/components/src/layout/layout.component.ts b/libs/components/src/layout/layout.component.ts index 7bf8a6ad173..99e31f2b64e 100644 --- a/libs/components/src/layout/layout.component.ts +++ b/libs/components/src/layout/layout.component.ts @@ -11,7 +11,6 @@ import { SharedModule } from "../shared"; @Component({ selector: "bit-layout", templateUrl: "layout.component.html", - standalone: true, imports: [CommonModule, SharedModule, LinkModule, RouterModule, PortalModule], hostDirectives: [DrawerHostDirective], }) diff --git a/libs/components/src/link/link.directive.ts b/libs/components/src/link/link.directive.ts index ca25e5fef56..ad9c94b7831 100644 --- a/libs/components/src/link/link.directive.ts +++ b/libs/components/src/link/link.directive.ts @@ -76,7 +76,6 @@ abstract class LinkDirective { */ @Directive({ selector: "a[bitLink]", - standalone: true, }) export class AnchorLinkDirective extends LinkDirective { @HostBinding("class") get classList() { @@ -88,7 +87,6 @@ export class AnchorLinkDirective extends LinkDirective { @Directive({ selector: "button[bitLink]", - standalone: true, }) export class ButtonLinkDirective extends LinkDirective { @HostBinding("class") get classList() { diff --git a/libs/components/src/menu/menu-divider.component.ts b/libs/components/src/menu/menu-divider.component.ts index 55b5c013c93..194506ee50f 100644 --- a/libs/components/src/menu/menu-divider.component.ts +++ b/libs/components/src/menu/menu-divider.component.ts @@ -3,6 +3,5 @@ import { Component } from "@angular/core"; @Component({ selector: "bit-menu-divider", templateUrl: "./menu-divider.component.html", - standalone: true, }) export class MenuDividerComponent {} diff --git a/libs/components/src/menu/menu-item.directive.ts b/libs/components/src/menu/menu-item.directive.ts index d0975e8e391..b52bbed2d50 100644 --- a/libs/components/src/menu/menu-item.directive.ts +++ b/libs/components/src/menu/menu-item.directive.ts @@ -6,7 +6,6 @@ import { Component, ElementRef, HostBinding, Input } from "@angular/core"; @Component({ selector: "[bitMenuItem]", templateUrl: "menu-item.component.html", - standalone: true, imports: [NgClass], }) export class MenuItemDirective implements FocusableOption { diff --git a/libs/components/src/menu/menu.component.ts b/libs/components/src/menu/menu.component.ts index a39dceb4454..8636f158729 100644 --- a/libs/components/src/menu/menu.component.ts +++ b/libs/components/src/menu/menu.component.ts @@ -19,7 +19,6 @@ import { MenuItemDirective } from "./menu-item.directive"; selector: "bit-menu", templateUrl: "./menu.component.html", exportAs: "menuComponent", - standalone: true, imports: [CdkTrapFocus], }) export class MenuComponent implements AfterContentInit { diff --git a/libs/components/src/multi-select/multi-select.component.ts b/libs/components/src/multi-select/multi-select.component.ts index cd92eb1d7ae..6fd87483780 100644 --- a/libs/components/src/multi-select/multi-select.component.ts +++ b/libs/components/src/multi-select/multi-select.component.ts @@ -37,7 +37,6 @@ let nextId = 0; selector: "bit-multi-select", templateUrl: "./multi-select.component.html", providers: [{ provide: BitFormFieldControl, useExisting: MultiSelectComponent }], - standalone: true, imports: [NgSelectModule, ReactiveFormsModule, FormsModule, BadgeModule, I18nPipe], }) /** diff --git a/libs/components/src/navigation/nav-divider.component.ts b/libs/components/src/navigation/nav-divider.component.ts index eff381e1c94..52fb433c54d 100644 --- a/libs/components/src/navigation/nav-divider.component.ts +++ b/libs/components/src/navigation/nav-divider.component.ts @@ -6,7 +6,6 @@ import { SideNavService } from "./side-nav.service"; @Component({ selector: "bit-nav-divider", templateUrl: "./nav-divider.component.html", - standalone: true, imports: [CommonModule], }) export class NavDividerComponent { diff --git a/libs/components/src/navigation/nav-group.component.ts b/libs/components/src/navigation/nav-group.component.ts index 37244f37c8d..5346a583e37 100644 --- a/libs/components/src/navigation/nav-group.component.ts +++ b/libs/components/src/navigation/nav-group.component.ts @@ -27,7 +27,6 @@ import { SideNavService } from "./side-nav.service"; { provide: NavBaseComponent, useExisting: NavGroupComponent }, { provide: NavGroupAbstraction, useExisting: NavGroupComponent }, ], - standalone: true, imports: [CommonModule, NavItemComponent, IconButtonModule, I18nPipe], }) export class NavGroupComponent extends NavBaseComponent implements AfterContentInit { diff --git a/libs/components/src/navigation/nav-group.stories.ts b/libs/components/src/navigation/nav-group.stories.ts index b2f6b0b6b99..19fe3764852 100644 --- a/libs/components/src/navigation/nav-group.stories.ts +++ b/libs/components/src/navigation/nav-group.stories.ts @@ -13,7 +13,6 @@ import { NavGroupComponent } from "./nav-group.component"; import { NavigationModule } from "./navigation.module"; @Component({ - standalone: true, template: "", }) class DummyContentComponent {} diff --git a/libs/components/src/navigation/nav-item.component.ts b/libs/components/src/navigation/nav-item.component.ts index c84aacf615e..23791c5b979 100644 --- a/libs/components/src/navigation/nav-item.component.ts +++ b/libs/components/src/navigation/nav-item.component.ts @@ -17,7 +17,6 @@ export abstract class NavGroupAbstraction { selector: "bit-nav-item", templateUrl: "./nav-item.component.html", providers: [{ provide: NavBaseComponent, useExisting: NavItemComponent }], - standalone: true, imports: [CommonModule, IconButtonModule, RouterModule], }) export class NavItemComponent extends NavBaseComponent { diff --git a/libs/components/src/navigation/nav-logo.component.ts b/libs/components/src/navigation/nav-logo.component.ts index de9d801e553..724406eeed5 100644 --- a/libs/components/src/navigation/nav-logo.component.ts +++ b/libs/components/src/navigation/nav-logo.component.ts @@ -13,7 +13,6 @@ import { SideNavService } from "./side-nav.service"; @Component({ selector: "bit-nav-logo", templateUrl: "./nav-logo.component.html", - standalone: true, imports: [RouterLinkActive, RouterLink, BitIconComponent, NavItemComponent], }) export class NavLogoComponent { diff --git a/libs/components/src/navigation/side-nav.component.ts b/libs/components/src/navigation/side-nav.component.ts index e8e4f131d6d..49b08c63f7c 100644 --- a/libs/components/src/navigation/side-nav.component.ts +++ b/libs/components/src/navigation/side-nav.component.ts @@ -16,7 +16,6 @@ export type SideNavVariant = "primary" | "secondary"; @Component({ selector: "bit-side-nav", templateUrl: "side-nav.component.html", - standalone: true, imports: [CommonModule, CdkTrapFocus, NavDividerComponent, BitIconButtonComponent, I18nPipe], }) export class SideNavComponent { diff --git a/libs/components/src/no-items/no-items.component.ts b/libs/components/src/no-items/no-items.component.ts index ee9e0ee0581..20ce95a53ba 100644 --- a/libs/components/src/no-items/no-items.component.ts +++ b/libs/components/src/no-items/no-items.component.ts @@ -9,7 +9,6 @@ import { BitIconComponent } from "../icon/icon.component"; @Component({ selector: "bit-no-items", templateUrl: "./no-items.component.html", - standalone: true, imports: [BitIconComponent], }) export class NoItemsComponent { diff --git a/libs/components/src/popover/popover-trigger-for.directive.ts b/libs/components/src/popover/popover-trigger-for.directive.ts index 482308c94d8..47fda1ca267 100644 --- a/libs/components/src/popover/popover-trigger-for.directive.ts +++ b/libs/components/src/popover/popover-trigger-for.directive.ts @@ -19,7 +19,6 @@ import { PopoverComponent } from "./popover.component"; @Directive({ selector: "[bitPopoverTriggerFor]", - standalone: true, exportAs: "popoverTrigger", }) export class PopoverTriggerForDirective implements OnDestroy, AfterViewInit { diff --git a/libs/components/src/popover/popover.component.ts b/libs/components/src/popover/popover.component.ts index e0ff4cf33d4..2c8f6fc3714 100644 --- a/libs/components/src/popover/popover.component.ts +++ b/libs/components/src/popover/popover.component.ts @@ -8,7 +8,6 @@ import { SharedModule } from "../shared/shared.module"; import { TypographyModule } from "../typography"; @Component({ - standalone: true, selector: "bit-popover", imports: [A11yModule, IconButtonModule, SharedModule, TypographyModule], templateUrl: "./popover.component.html", diff --git a/libs/components/src/progress/progress.component.ts b/libs/components/src/progress/progress.component.ts index cc2a6df7340..125d05025a2 100644 --- a/libs/components/src/progress/progress.component.ts +++ b/libs/components/src/progress/progress.component.ts @@ -23,7 +23,6 @@ const BackgroundClasses: Record<BackgroundType, string[]> = { @Component({ selector: "bit-progress", templateUrl: "./progress.component.html", - standalone: true, imports: [CommonModule], }) export class ProgressComponent { diff --git a/libs/components/src/radio-button/radio-button.component.ts b/libs/components/src/radio-button/radio-button.component.ts index 2db206bbaf9..a319f86840b 100644 --- a/libs/components/src/radio-button/radio-button.component.ts +++ b/libs/components/src/radio-button/radio-button.component.ts @@ -10,7 +10,6 @@ let nextId = 0; @Component({ selector: "bit-radio-button", templateUrl: "radio-button.component.html", - standalone: true, imports: [FormControlModule, RadioInputComponent], }) export class RadioButtonComponent { diff --git a/libs/components/src/radio-button/radio-group.component.ts b/libs/components/src/radio-button/radio-group.component.ts index 895f769af50..51c7c5e9c92 100644 --- a/libs/components/src/radio-button/radio-group.component.ts +++ b/libs/components/src/radio-button/radio-group.component.ts @@ -13,7 +13,6 @@ let nextId = 0; @Component({ selector: "bit-radio-group", templateUrl: "radio-group.component.html", - standalone: true, imports: [NgTemplateOutlet, I18nPipe], }) export class RadioGroupComponent implements ControlValueAccessor { diff --git a/libs/components/src/radio-button/radio-input.component.ts b/libs/components/src/radio-button/radio-input.component.ts index 5473f70394e..53bda5566b7 100644 --- a/libs/components/src/radio-button/radio-input.component.ts +++ b/libs/components/src/radio-button/radio-input.component.ts @@ -11,7 +11,6 @@ let nextId = 0; selector: "input[type=radio][bitRadio]", template: "", providers: [{ provide: BitFormControlAbstraction, useExisting: RadioInputComponent }], - standalone: true, }) export class RadioInputComponent implements BitFormControlAbstraction { @HostBinding("attr.id") @Input() id = `bit-radio-input-${nextId++}`; diff --git a/libs/components/src/search/search.component.ts b/libs/components/src/search/search.component.ts index 7edf3b1d60a..0cdd9c611fa 100644 --- a/libs/components/src/search/search.component.ts +++ b/libs/components/src/search/search.component.ts @@ -30,7 +30,6 @@ let nextId = 0; useExisting: SearchComponent, }, ], - standalone: true, imports: [InputModule, ReactiveFormsModule, FormsModule, I18nPipe], }) export class SearchComponent implements ControlValueAccessor, FocusableElement { diff --git a/libs/components/src/section/section-header.component.ts b/libs/components/src/section/section-header.component.ts index 9f7b1a21f16..c96f9486b52 100644 --- a/libs/components/src/section/section-header.component.ts +++ b/libs/components/src/section/section-header.component.ts @@ -3,7 +3,6 @@ import { Component } from "@angular/core"; import { TypographyModule } from "../typography"; @Component({ - standalone: true, selector: "bit-section-header", templateUrl: "./section-header.component.html", imports: [TypographyModule], diff --git a/libs/components/src/section/section.component.ts b/libs/components/src/section/section.component.ts index ec34c804119..c6a5c1d7cb5 100644 --- a/libs/components/src/section/section.component.ts +++ b/libs/components/src/section/section.component.ts @@ -4,7 +4,6 @@ import { Component, Input } from "@angular/core"; @Component({ selector: "bit-section", - standalone: true, imports: [CommonModule], template: ` <section diff --git a/libs/components/src/select/option.component.ts b/libs/components/src/select/option.component.ts index 841ceda3648..b32b124be25 100644 --- a/libs/components/src/select/option.component.ts +++ b/libs/components/src/select/option.component.ts @@ -7,7 +7,6 @@ import { Option } from "./option"; @Component({ selector: "bit-option", template: `<ng-template><ng-content></ng-content></ng-template>`, - standalone: true, }) export class OptionComponent<T = unknown> implements Option<T> { @Input() diff --git a/libs/components/src/select/select.component.spec.ts b/libs/components/src/select/select.component.spec.ts index 47f0a8c7b13..236a788549a 100644 --- a/libs/components/src/select/select.component.spec.ts +++ b/libs/components/src/select/select.component.spec.ts @@ -10,7 +10,6 @@ import { SelectComponent } from "./select.component"; import { SelectModule } from "./select.module"; @Component({ - standalone: true, imports: [SelectModule, ReactiveFormsModule], template: ` <form [formGroup]="form"> diff --git a/libs/components/src/select/select.component.ts b/libs/components/src/select/select.component.ts index 8b656af3465..d2c48bf0f6e 100644 --- a/libs/components/src/select/select.component.ts +++ b/libs/components/src/select/select.component.ts @@ -35,7 +35,6 @@ let nextId = 0; selector: "bit-select", templateUrl: "select.component.html", providers: [{ provide: BitFormFieldControl, useExisting: SelectComponent }], - standalone: true, imports: [NgSelectModule, ReactiveFormsModule, FormsModule], }) export class SelectComponent<T> implements BitFormFieldControl, ControlValueAccessor { diff --git a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts index 7709506f050..4a8c2b06953 100644 --- a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts @@ -8,7 +8,6 @@ import { TableDataSource, TableModule } from "../../../table"; @Component({ selector: "dialog-virtual-scroll-block", - standalone: true, imports: [DialogModule, IconButtonModule, SectionComponent, TableModule, ScrollingModule], template: /*html*/ `<bit-section> <cdk-virtual-scroll-viewport scrollWindow itemSize="47"> diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts index babc4365c8e..316dbf22d66 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts @@ -8,7 +8,6 @@ import { I18nMockService } from "../../../utils/i18n-mock.service"; import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; @Component({ - standalone: true, selector: "bit-kitchen-sink-form", imports: [KitchenSinkSharedModule], providers: [ diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts index 70f56d2e28d..7fc222bd036 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts @@ -9,7 +9,6 @@ import { KitchenSinkTable } from "./kitchen-sink-table.component"; import { KitchenSinkToggleList } from "./kitchen-sink-toggle-list.component"; @Component({ - standalone: true, imports: [KitchenSinkSharedModule], template: ` <bit-dialog title="Dialog Title" dialogSize="large"> @@ -26,7 +25,6 @@ class KitchenSinkDialog { } @Component({ - standalone: true, selector: "bit-tab-main", imports: [KitchenSinkSharedModule, KitchenSinkTable, KitchenSinkToggleList, KitchenSinkForm], template: ` diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts index ba71483d7de..8765eae9960 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts @@ -3,7 +3,6 @@ import { Component } from "@angular/core"; import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; @Component({ - standalone: true, selector: "bit-kitchen-sink-table", imports: [KitchenSinkSharedModule], template: ` diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts index c71140d8166..ec8787af1bd 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts @@ -3,7 +3,6 @@ import { Component } from "@angular/core"; import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; @Component({ - standalone: true, selector: "bit-kitchen-sink-toggle-list", imports: [KitchenSinkSharedModule], template: ` diff --git a/libs/components/src/table/cell.directive.ts b/libs/components/src/table/cell.directive.ts index 8928fe7c095..61c75571063 100644 --- a/libs/components/src/table/cell.directive.ts +++ b/libs/components/src/table/cell.directive.ts @@ -2,7 +2,6 @@ import { Directive, HostBinding } from "@angular/core"; @Directive({ selector: "th[bitCell], td[bitCell]", - standalone: true, }) export class CellDirective { @HostBinding("class") get classList() { diff --git a/libs/components/src/table/row.directive.ts b/libs/components/src/table/row.directive.ts index 23347224af9..19f3d3f775b 100644 --- a/libs/components/src/table/row.directive.ts +++ b/libs/components/src/table/row.directive.ts @@ -2,7 +2,6 @@ import { Directive, HostBinding, Input } from "@angular/core"; @Directive({ selector: "tr[bitRow]", - standalone: true, }) export class RowDirective { @Input() alignContent: "top" | "middle" | "bottom" | "baseline" = "middle"; diff --git a/libs/components/src/table/sortable.component.ts b/libs/components/src/table/sortable.component.ts index 1d2a2c07d6f..d36b60dc014 100644 --- a/libs/components/src/table/sortable.component.ts +++ b/libs/components/src/table/sortable.component.ts @@ -20,7 +20,6 @@ import { TableComponent } from "./table.component"; <i class="bwi tw-ms-2" [ngClass]="icon"></i> </button> `, - standalone: true, imports: [NgClass], }) export class SortableComponent implements OnInit { diff --git a/libs/components/src/table/table-scroll.component.ts b/libs/components/src/table/table-scroll.component.ts index e01bf168cb1..b463b12f6ce 100644 --- a/libs/components/src/table/table-scroll.component.ts +++ b/libs/components/src/table/table-scroll.component.ts @@ -35,7 +35,6 @@ import { TableComponent } from "./table.component"; */ @Directive({ selector: "[bitRowDef]", - standalone: true, }) export class BitRowDef { constructor(public template: TemplateRef<any>) {} @@ -50,7 +49,6 @@ export class BitRowDef { selector: "bit-table-scroll", templateUrl: "./table-scroll.component.html", providers: [{ provide: TableComponent, useExisting: TableScrollComponent }], - standalone: true, imports: [ CommonModule, CdkVirtualScrollViewport, diff --git a/libs/components/src/table/table.component.ts b/libs/components/src/table/table.component.ts index cd0a2a6c65e..8029e8461f9 100644 --- a/libs/components/src/table/table.component.ts +++ b/libs/components/src/table/table.component.ts @@ -17,7 +17,6 @@ import { TableDataSource } from "./table-data-source"; @Directive({ selector: "ng-template[body]", - standalone: true, }) export class TableBodyDirective { // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility @@ -27,7 +26,6 @@ export class TableBodyDirective { @Component({ selector: "bit-table", templateUrl: "./table.component.html", - standalone: true, imports: [CommonModule], }) export class TableComponent implements OnDestroy, AfterContentChecked { diff --git a/libs/components/src/tabs/shared/tab-header.component.ts b/libs/components/src/tabs/shared/tab-header.component.ts index 077ee2b8aa6..24dcd203c6b 100644 --- a/libs/components/src/tabs/shared/tab-header.component.ts +++ b/libs/components/src/tabs/shared/tab-header.component.ts @@ -10,6 +10,5 @@ import { Component } from "@angular/core"; "tw-h-16 tw-ps-4 tw-bg-background-alt tw-flex tw-items-end tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300", }, template: `<ng-content></ng-content>`, - standalone: true, }) export class TabHeaderComponent {} diff --git a/libs/components/src/tabs/shared/tab-list-container.directive.ts b/libs/components/src/tabs/shared/tab-list-container.directive.ts index cedae44e582..1cf8a762d58 100644 --- a/libs/components/src/tabs/shared/tab-list-container.directive.ts +++ b/libs/components/src/tabs/shared/tab-list-container.directive.ts @@ -8,6 +8,5 @@ import { Directive } from "@angular/core"; host: { class: "tw-inline-flex tw-flex-wrap tw-leading-5", }, - standalone: true, }) export class TabListContainerDirective {} diff --git a/libs/components/src/tabs/shared/tab-list-item.directive.ts b/libs/components/src/tabs/shared/tab-list-item.directive.ts index 2a71a385a83..931ad51c1c8 100644 --- a/libs/components/src/tabs/shared/tab-list-item.directive.ts +++ b/libs/components/src/tabs/shared/tab-list-item.directive.ts @@ -9,7 +9,6 @@ import { Directive, ElementRef, HostBinding, Input } from "@angular/core"; */ @Directive({ selector: "[bitTabListItem]", - standalone: true, }) export class TabListItemDirective implements FocusableOption { @Input() active: boolean; diff --git a/libs/components/src/tabs/tab-group/tab-body.component.ts b/libs/components/src/tabs/tab-group/tab-body.component.ts index 45a6a05e7c2..3c14d333258 100644 --- a/libs/components/src/tabs/tab-group/tab-body.component.ts +++ b/libs/components/src/tabs/tab-group/tab-body.component.ts @@ -6,7 +6,6 @@ import { Component, HostBinding, Input } from "@angular/core"; @Component({ selector: "bit-tab-body", templateUrl: "tab-body.component.html", - standalone: true, imports: [CdkPortalOutlet], }) export class TabBodyComponent { diff --git a/libs/components/src/tabs/tab-group/tab-group.component.ts b/libs/components/src/tabs/tab-group/tab-group.component.ts index b525b9b6723..ae7fa12143e 100644 --- a/libs/components/src/tabs/tab-group/tab-group.component.ts +++ b/libs/components/src/tabs/tab-group/tab-group.component.ts @@ -31,7 +31,6 @@ let nextId = 0; @Component({ selector: "bit-tab-group", templateUrl: "./tab-group.component.html", - standalone: true, imports: [ NgTemplateOutlet, TabHeaderComponent, diff --git a/libs/components/src/tabs/tab-group/tab-label.directive.ts b/libs/components/src/tabs/tab-group/tab-label.directive.ts index 9a0e59845a1..45da163631b 100644 --- a/libs/components/src/tabs/tab-group/tab-label.directive.ts +++ b/libs/components/src/tabs/tab-group/tab-label.directive.ts @@ -16,7 +16,6 @@ import { Directive, TemplateRef } from "@angular/core"; */ @Directive({ selector: "[bitTabLabel]", - standalone: true, }) export class TabLabelDirective { constructor(public templateRef: TemplateRef<unknown>) {} diff --git a/libs/components/src/tabs/tab-group/tab.component.ts b/libs/components/src/tabs/tab-group/tab.component.ts index b2c9b1999bc..260cb0c8193 100644 --- a/libs/components/src/tabs/tab-group/tab.component.ts +++ b/libs/components/src/tabs/tab-group/tab.component.ts @@ -19,7 +19,6 @@ import { TabLabelDirective } from "./tab-label.directive"; host: { role: "tabpanel", }, - standalone: true, }) export class TabComponent implements OnInit { @Input() disabled = false; diff --git a/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts b/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts index 0dac6681475..3ba0d651f76 100644 --- a/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts +++ b/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts @@ -12,7 +12,6 @@ import { TabNavBarComponent } from "./tab-nav-bar.component"; @Component({ selector: "bit-tab-link", templateUrl: "tab-link.component.html", - standalone: true, imports: [TabListItemDirective, RouterModule], }) export class TabLinkComponent implements FocusableOption, AfterViewInit, OnDestroy { diff --git a/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts b/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts index 305196a0c69..1f3292054e7 100644 --- a/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts +++ b/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts @@ -21,7 +21,6 @@ import { TabLinkComponent } from "./tab-link.component"; host: { class: "tw-block", }, - standalone: true, imports: [TabHeaderComponent, TabListContainerDirective], }) export class TabNavBarComponent implements AfterContentInit { diff --git a/libs/components/src/toast/toast-container.component.ts b/libs/components/src/toast/toast-container.component.ts index 1cd33f67ac7..d2995e25626 100644 --- a/libs/components/src/toast/toast-container.component.ts +++ b/libs/components/src/toast/toast-container.component.ts @@ -4,7 +4,6 @@ import { ToastContainerDirective, ToastrService } from "ngx-toastr"; @Component({ selector: "bit-toast-container", templateUrl: "toast-container.component.html", - standalone: true, imports: [ToastContainerDirective], }) export class ToastContainerComponent implements OnInit { diff --git a/libs/components/src/toast/toast.component.ts b/libs/components/src/toast/toast.component.ts index bbf0291f180..954f09eb0fb 100644 --- a/libs/components/src/toast/toast.component.ts +++ b/libs/components/src/toast/toast.component.ts @@ -28,7 +28,6 @@ const variants: Record<ToastVariant, { icon: string; bgColor: string }> = { @Component({ selector: "bit-toast", templateUrl: "toast.component.html", - standalone: true, imports: [SharedModule, IconButtonModule, TypographyModule], }) export class ToastComponent { diff --git a/libs/components/src/toast/toastr.component.ts b/libs/components/src/toast/toastr.component.ts index 06182f094aa..3b7665f1d64 100644 --- a/libs/components/src/toast/toastr.component.ts +++ b/libs/components/src/toast/toastr.component.ts @@ -26,7 +26,6 @@ import { ToastComponent } from "./toast.component"; transition("active => removed", animate("{{ easeTime }}ms {{ easing }}")), ]), ], - standalone: true, imports: [ToastComponent], }) export class BitwardenToastrComponent extends BaseToastrComponent { diff --git a/libs/components/src/toggle-group/toggle-group.component.ts b/libs/components/src/toggle-group/toggle-group.component.ts index 057a594654a..0b4cb059216 100644 --- a/libs/components/src/toggle-group/toggle-group.component.ts +++ b/libs/components/src/toggle-group/toggle-group.component.ts @@ -12,7 +12,6 @@ let nextId = 0; @Component({ selector: "bit-toggle-group", templateUrl: "./toggle-group.component.html", - standalone: true, }) export class ToggleGroupComponent<TValue = unknown> { private id = nextId++; diff --git a/libs/components/src/toggle-group/toggle.component.ts b/libs/components/src/toggle-group/toggle.component.ts index a9e32beb4af..03bd8e43404 100644 --- a/libs/components/src/toggle-group/toggle.component.ts +++ b/libs/components/src/toggle-group/toggle.component.ts @@ -19,7 +19,6 @@ let nextId = 0; @Component({ selector: "bit-toggle", templateUrl: "./toggle.component.html", - standalone: true, imports: [NgClass], }) export class ToggleComponent<TValue> implements AfterContentChecked, AfterViewInit { diff --git a/libs/components/src/typography/typography.directive.ts b/libs/components/src/typography/typography.directive.ts index 36d6b996dbe..e48ef67001f 100644 --- a/libs/components/src/typography/typography.directive.ts +++ b/libs/components/src/typography/typography.directive.ts @@ -31,7 +31,6 @@ const margins: Record<TypographyType, string[]> = { @Directive({ selector: "[bitTypography]", - standalone: true, }) export class TypographyDirective { @Input("bitTypography") bitTypography: TypographyType; diff --git a/libs/ui/common/src/i18n.pipe.ts b/libs/ui/common/src/i18n.pipe.ts index fdcfec0ceac..ec30bd85092 100644 --- a/libs/ui/common/src/i18n.pipe.ts +++ b/libs/ui/common/src/i18n.pipe.ts @@ -13,7 +13,6 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic */ @Pipe({ name: "i18n", - standalone: true, }) export class I18nPipe implements PipeTransform { constructor(private i18nService: I18nService) {} From 14e363ad86c8f3f8c13c7301afa2b34ccd7f633f Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Mon, 2 Jun 2025 21:38:20 +0200 Subject: [PATCH 216/245] Remove standalone true from km (#15042) Remove standalone: true from every instance since it's the default as of Angular 19. --- .../migrate-encryption/migrate-legacy-encryption.component.ts | 1 - .../src/key-rotation/key-rotation-trust-info.component.ts | 1 - libs/key-management-ui/src/lock/components/lock.component.ts | 1 - .../src/trust/account-recovery-trust.component.ts | 1 - .../src/trust/emergency-access-trust.component.ts | 1 - 5 files changed, 5 deletions(-) diff --git a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts b/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts index 62456d96401..f6685a749a2 100644 --- a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts +++ b/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts @@ -19,7 +19,6 @@ import { UserKeyRotationService } from "../key-rotation/user-key-rotation.servic // The master key was originally used to encrypt user data, before the user key was introduced. // This component is used to migrate from the old encryption scheme to the new one. @Component({ - standalone: true, imports: [SharedModule, UserKeyRotationModule], templateUrl: "migrate-legacy-encryption.component.html", }) diff --git a/libs/key-management-ui/src/key-rotation/key-rotation-trust-info.component.ts b/libs/key-management-ui/src/key-rotation/key-rotation-trust-info.component.ts index 51e6058ae5b..a2d3de3b30f 100644 --- a/libs/key-management-ui/src/key-rotation/key-rotation-trust-info.component.ts +++ b/libs/key-management-ui/src/key-rotation/key-rotation-trust-info.component.ts @@ -20,7 +20,6 @@ type KeyRotationTrustDialogData = { @Component({ selector: "key-rotation-trust-info", templateUrl: "key-rotation-trust-info.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/key-management-ui/src/lock/components/lock.component.ts b/libs/key-management-ui/src/lock/components/lock.component.ts index 3cb0dbaca52..ef91f2a03f1 100644 --- a/libs/key-management-ui/src/lock/components/lock.component.ts +++ b/libs/key-management-ui/src/lock/components/lock.component.ts @@ -77,7 +77,6 @@ const AUTOPROMPT_BIOMETRICS_PROCESS_RELOAD_DELAY = 5000; @Component({ selector: "bit-lock", templateUrl: "lock.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/key-management-ui/src/trust/account-recovery-trust.component.ts b/libs/key-management-ui/src/trust/account-recovery-trust.component.ts index e1ec390a460..8eec776bbb6 100644 --- a/libs/key-management-ui/src/trust/account-recovery-trust.component.ts +++ b/libs/key-management-ui/src/trust/account-recovery-trust.component.ts @@ -28,7 +28,6 @@ type AccountRecoveryTrustDialogData = { @Component({ selector: "account-recovery-trust", templateUrl: "account-recovery-trust.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/key-management-ui/src/trust/emergency-access-trust.component.ts b/libs/key-management-ui/src/trust/emergency-access-trust.component.ts index 29ee64798e9..35c6b16c873 100644 --- a/libs/key-management-ui/src/trust/emergency-access-trust.component.ts +++ b/libs/key-management-ui/src/trust/emergency-access-trust.component.ts @@ -28,7 +28,6 @@ type EmergencyAccessTrustDialogData = { @Component({ selector: "emergency-access-trust", templateUrl: "emergency-access-trust.component.html", - standalone: true, imports: [ CommonModule, JslibModule, From 8967fc21db417473c319f0402ab81b98bc703b8a Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Mon, 2 Jun 2025 21:40:06 +0200 Subject: [PATCH 217/245] Remove standalone true from billing (#15038) --- apps/browser/src/billing/popup/settings/premium-v2.component.ts | 1 - .../accounts/trial-initiation/trial-billing-step.component.ts | 1 - .../src/app/billing/members/add-sponsorship-dialog.component.ts | 1 - .../app/billing/organizations/change-plan-dialog.component.ts | 1 - .../app/billing/organizations/organization-plans.component.ts | 1 - .../billing/shared/billing-free-families-nav-item.component.ts | 1 - .../src/app/billing/shared/payment/payment-label.component.ts | 1 - apps/web/src/app/billing/shared/payment/payment.component.ts | 1 - apps/web/src/app/billing/shared/tax-info.component.ts | 1 - .../shared/verify-bank-account/verify-bank-account.component.ts | 1 - .../web/src/app/billing/warnings/free-trial-warning.component.ts | 1 - .../app/billing/warnings/reseller-renewal-warning.component.ts | 1 - .../app/billing/providers/clients/manage-clients.component.ts | 1 - .../src/app/billing/providers/clients/no-clients.component.ts | 1 - .../bit-web/src/app/billing/providers/clients/replace.pipe.ts | 1 - 15 files changed, 15 deletions(-) diff --git a/apps/browser/src/billing/popup/settings/premium-v2.component.ts b/apps/browser/src/billing/popup/settings/premium-v2.component.ts index ff0e8efd646..fde44688349 100644 --- a/apps/browser/src/billing/popup/settings/premium-v2.component.ts +++ b/apps/browser/src/billing/popup/settings/premium-v2.component.ts @@ -29,7 +29,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co @Component({ selector: "app-premium", templateUrl: "premium-v2.component.html", - standalone: true, imports: [ ButtonModule, CardComponent, diff --git a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts index 63c42139648..fda7faeeb25 100644 --- a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts +++ b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts @@ -68,7 +68,6 @@ export enum SubscriptionProduct { selector: "app-trial-billing-step", templateUrl: "trial-billing-step.component.html", imports: [BillingSharedModule], - standalone: true, }) export class TrialBillingStepComponent implements OnInit, OnDestroy { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; diff --git a/apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts b/apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts index 7e6c0d464c3..f3c01e41dbb 100644 --- a/apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts +++ b/apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts @@ -38,7 +38,6 @@ interface AddSponsorshipDialogParams { @Component({ templateUrl: "add-sponsorship-dialog.component.html", - standalone: true, imports: [ JslibModule, ButtonModule, diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts index a6e8670d944..f6e271f2347 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts @@ -109,7 +109,6 @@ interface OnSuccessArgs { @Component({ templateUrl: "./change-plan-dialog.component.html", - standalone: true, imports: [BillingSharedModule], }) export class ChangePlanDialogComponent implements OnInit, OnDestroy { diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index aad3b8df763..ca8db39a047 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -77,7 +77,6 @@ const Allowed2020PlansForLegacyProviders = [ @Component({ selector: "app-organization-plans", templateUrl: "organization-plans.component.html", - standalone: true, imports: [BillingSharedModule, OrganizationCreateModule], }) export class OrganizationPlansComponent implements OnInit, OnDestroy { diff --git a/apps/web/src/app/billing/shared/billing-free-families-nav-item.component.ts b/apps/web/src/app/billing/shared/billing-free-families-nav-item.component.ts index ad322645270..60b46c2b64e 100644 --- a/apps/web/src/app/billing/shared/billing-free-families-nav-item.component.ts +++ b/apps/web/src/app/billing/shared/billing-free-families-nav-item.component.ts @@ -10,7 +10,6 @@ import { BillingSharedModule } from "./billing-shared.module"; @Component({ selector: "billing-free-families-nav-item", templateUrl: "./billing-free-families-nav-item.component.html", - standalone: true, imports: [NavigationModule, BillingSharedModule], }) export class BillingFreeFamiliesNavItemComponent { diff --git a/apps/web/src/app/billing/shared/payment/payment-label.component.ts b/apps/web/src/app/billing/shared/payment/payment-label.component.ts index 80a4087456d..025727d1d1e 100644 --- a/apps/web/src/app/billing/shared/payment/payment-label.component.ts +++ b/apps/web/src/app/billing/shared/payment/payment-label.component.ts @@ -14,7 +14,6 @@ import { SharedModule } from "../../../shared"; @Component({ selector: "app-payment-label", templateUrl: "./payment-label.component.html", - standalone: true, imports: [FormFieldModule, SharedModule], }) export class PaymentLabelComponent { diff --git a/apps/web/src/app/billing/shared/payment/payment.component.ts b/apps/web/src/app/billing/shared/payment/payment.component.ts index 75db7779a04..afb67dec883 100644 --- a/apps/web/src/app/billing/shared/payment/payment.component.ts +++ b/apps/web/src/app/billing/shared/payment/payment.component.ts @@ -22,7 +22,6 @@ import { PaymentLabelComponent } from "./payment-label.component"; @Component({ selector: "app-payment", templateUrl: "./payment.component.html", - standalone: true, imports: [BillingServicesModule, SharedModule, PaymentLabelComponent], }) export class PaymentComponent implements OnInit, OnDestroy { diff --git a/apps/web/src/app/billing/shared/tax-info.component.ts b/apps/web/src/app/billing/shared/tax-info.component.ts index 74e2ab35cb9..35c4a3fcc4e 100644 --- a/apps/web/src/app/billing/shared/tax-info.component.ts +++ b/apps/web/src/app/billing/shared/tax-info.component.ts @@ -21,7 +21,6 @@ import { SharedModule } from "../../shared"; @Component({ selector: "app-tax-info", templateUrl: "tax-info.component.html", - standalone: true, imports: [SharedModule], }) export class TaxInfoComponent implements OnInit, OnDestroy { diff --git a/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.ts b/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.ts index d2cd473d3d3..b7cdfbe60a2 100644 --- a/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.ts +++ b/apps/web/src/app/billing/shared/verify-bank-account/verify-bank-account.component.ts @@ -10,7 +10,6 @@ import { SharedModule } from "../../../shared"; @Component({ selector: "app-verify-bank-account", templateUrl: "./verify-bank-account.component.html", - standalone: true, imports: [SharedModule], }) export class VerifyBankAccountComponent { diff --git a/apps/web/src/app/billing/warnings/free-trial-warning.component.ts b/apps/web/src/app/billing/warnings/free-trial-warning.component.ts index e83873e9d6b..b000878bf66 100644 --- a/apps/web/src/app/billing/warnings/free-trial-warning.component.ts +++ b/apps/web/src/app/billing/warnings/free-trial-warning.component.ts @@ -37,7 +37,6 @@ import { </bit-banner> } `, - standalone: true, imports: [AnchorLinkDirective, AsyncPipe, BannerComponent, I18nPipe], }) export class FreeTrialWarningComponent implements OnInit { diff --git a/apps/web/src/app/billing/warnings/reseller-renewal-warning.component.ts b/apps/web/src/app/billing/warnings/reseller-renewal-warning.component.ts index fc94e85e28d..6bcfba5ce6c 100644 --- a/apps/web/src/app/billing/warnings/reseller-renewal-warning.component.ts +++ b/apps/web/src/app/billing/warnings/reseller-renewal-warning.component.ts @@ -27,7 +27,6 @@ import { </bit-banner> } `, - standalone: true, imports: [AsyncPipe, BannerComponent], }) export class ResellerRenewalWarningComponent implements OnInit { diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts index a57e6351349..de9e63cd509 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts @@ -51,7 +51,6 @@ import { ReplacePipe } from "./replace.pipe"; @Component({ templateUrl: "manage-clients.component.html", - standalone: true, imports: [ AvatarModule, TableModule, diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts index 7e4323d7603..768f22c5738 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts @@ -24,7 +24,6 @@ const gearIcon = svgIcon` @Component({ selector: "app-no-clients", - standalone: true, imports: [SharedOrganizationModule], template: `<div class="tw-flex tw-flex-col tw-items-center tw-text-info"> <bit-icon [icon]="icon"></bit-icon> diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/replace.pipe.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/replace.pipe.ts index 4a06e85f533..ad8c6a8940c 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/replace.pipe.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/replace.pipe.ts @@ -2,7 +2,6 @@ import { Pipe, PipeTransform } from "@angular/core"; @Pipe({ name: "replace", - standalone: true, }) export class ReplacePipe implements PipeTransform { transform(value: string, pattern: string, replacement: string): string { From 8b46e33e97bf8b4bb564241df67fc8df18067732 Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Mon, 2 Jun 2025 21:47:53 +0200 Subject: [PATCH 218/245] [CL-714] Remove standalone true from autofill (#15037) Remove standalone: true from every instance since it's the default as of Angular 19. --- .../src/autofill/popup/fido2/fido2-cipher-row.component.ts | 1 - .../src/autofill/popup/fido2/fido2-use-browser-link.component.ts | 1 - apps/browser/src/autofill/popup/fido2/fido2.component.ts | 1 - apps/browser/src/autofill/popup/settings/autofill.component.ts | 1 - .../src/autofill/popup/settings/blocked-domains.component.ts | 1 - .../src/autofill/popup/settings/excluded-domains.component.ts | 1 - .../src/autofill/popup/settings/notifications.component.ts | 1 - 7 files changed, 7 deletions(-) diff --git a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts index 02df3ffe9b3..074e23d642d 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts @@ -17,7 +17,6 @@ import { selector: "app-fido2-cipher-row", templateUrl: "fido2-cipher-row.component.html", changeDetection: ChangeDetectionStrategy.OnPush, - standalone: true, imports: [ BadgeModule, ButtonModule, diff --git a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts index 91d97ac96dc..27fe88130de 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts @@ -20,7 +20,6 @@ import { BrowserFido2UserInterfaceSession } from "../../fido2/services/browser-f @Component({ selector: "app-fido2-use-browser-link", templateUrl: "fido2-use-browser-link.component.html", - standalone: true, imports: [A11yModule, CdkConnectedOverlay, CdkOverlayOrigin, CommonModule, JslibModule], animations: [ trigger("transformPanel", [ diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index 996d1bb6176..3107b60f475 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -74,7 +74,6 @@ interface ViewData { @Component({ selector: "app-fido2", templateUrl: "fido2.component.html", - standalone: true, imports: [ ButtonModule, CommonModule, diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.ts b/apps/browser/src/autofill/popup/settings/autofill.component.ts index 8c5c8e600a0..9e83c3fc2c5 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill.component.ts @@ -66,7 +66,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co @Component({ templateUrl: "autofill.component.html", - standalone: true, imports: [ CardComponent, CheckboxModule, diff --git a/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts b/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts index c59ce24c7c4..15379eff436 100644 --- a/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts +++ b/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts @@ -44,7 +44,6 @@ import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popu @Component({ selector: "app-blocked-domains", templateUrl: "blocked-domains.component.html", - standalone: true, imports: [ ButtonModule, CardComponent, diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts index 504d2dbfc17..a5bfad726f5 100644 --- a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts +++ b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts @@ -45,7 +45,6 @@ import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popu @Component({ selector: "app-excluded-domains", templateUrl: "excluded-domains.component.html", - standalone: true, imports: [ ButtonModule, CardComponent, diff --git a/apps/browser/src/autofill/popup/settings/notifications.component.ts b/apps/browser/src/autofill/popup/settings/notifications.component.ts index 476b601c4e5..cb10dec620b 100644 --- a/apps/browser/src/autofill/popup/settings/notifications.component.ts +++ b/apps/browser/src/autofill/popup/settings/notifications.component.ts @@ -23,7 +23,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co @Component({ templateUrl: "notifications.component.html", - standalone: true, imports: [ CommonModule, JslibModule, From f3ff1e98ec417d8a2ba8d57f74cc93cec8c09211 Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Mon, 2 Jun 2025 22:22:57 +0200 Subject: [PATCH 219/245] Remove standalone true from vault (#15040) Remove standalone: true from every instance since it's the default as of Angular 19. --- .../at-risk-callout/at-risk-password-callout.component.ts | 1 - .../at-risk-carousel-dialog.component.ts | 1 - .../at-risk-passwords/at-risk-passwords.component.spec.ts | 3 --- .../at-risk-passwords/at-risk-passwords.component.ts | 1 - .../components/vault-v2/add-edit/add-edit-v2.component.ts | 1 - .../assign-collections/assign-collections.component.ts | 1 - .../vault-v2/attachments/attachments-v2.component.spec.ts | 2 -- .../vault-v2/attachments/attachments-v2.component.ts | 1 - .../attachments/open-attachments/open-attachments.component.ts | 1 - .../autofill-vault-list-items.component.ts | 1 - .../blocked-injection-banner.component.ts | 1 - .../vault-v2/intro-carousel/intro-carousel.component.ts | 1 - .../vault-v2/item-copy-action/item-copy-actions.component.ts | 1 - .../vault-v2/item-more-options/item-more-options.component.ts | 1 - .../new-item-dropdown/new-item-dropdown-v2.component.ts | 1 - .../vault-generator-dialog.component.spec.ts | 1 - .../vault-generator-dialog/vault-generator-dialog.component.ts | 1 - .../vault-v2/vault-header/vault-header-v2.component.ts | 1 - .../vault-list-filters/vault-list-filters.component.ts | 1 - .../vault-list-items-container.component.ts | 1 - .../vault-password-history-v2.component.ts | 1 - .../vault-v2/vault-search/vault-v2-search.component.ts | 1 - .../src/vault/popup/components/vault-v2/vault-v2.component.ts | 1 - .../popup/components/vault-v2/view-v2/view-v2.component.ts | 1 - .../src/vault/popup/settings/appearance-v2.component.spec.ts | 2 -- .../src/vault/popup/settings/appearance-v2.component.ts | 1 - .../src/vault/popup/settings/download-bitwarden.component.ts | 1 - .../src/vault/popup/settings/folders-v2.component.spec.ts | 2 -- apps/browser/src/vault/popup/settings/folders-v2.component.ts | 1 - .../popup/settings/more-from-bitwarden-page-v2.component.ts | 1 - .../trash-list-items-container.component.ts | 1 - apps/browser/src/vault/popup/settings/trash.component.ts | 1 - .../src/vault/popup/settings/vault-settings-v2.component.ts | 1 - .../vault/app/vault/credential-generator-dialog.component.ts | 1 - apps/desktop/src/vault/app/vault/item-footer.component.ts | 1 - apps/desktop/src/vault/app/vault/vault-items-v2.component.ts | 1 - apps/desktop/src/vault/app/vault/vault-v2.component.ts | 1 - .../assign-collections/assign-collections-web.component.ts | 1 - .../browser-extension-prompt-install.component.ts | 1 - .../browser-extension-prompt.component.ts | 1 - .../vault-item-dialog/vault-item-dialog.component.ts | 1 - .../web-generator-dialog.component.spec.ts | 1 - .../web-generator-dialog/web-generator-dialog.component.ts | 1 - .../src/app/vault/individual-vault/add-edit-v2.component.ts | 1 - .../individual-vault/vault-banners/vault-banners.component.ts | 1 - .../individual-vault/vault-header/vault-header.component.ts | 1 - .../vault-onboarding/vault-onboarding.component.ts | 1 - apps/web/src/app/vault/individual-vault/vault.component.ts | 1 - apps/web/src/app/vault/individual-vault/view.component.ts | 1 - .../src/vault/components/spotlight/spotlight.component.ts | 1 - .../additional-options-section.component.spec.ts | 1 - .../additional-options/additional-options-section.component.ts | 1 - .../attachments/cipher-attachments.component.spec.ts | 1 - .../components/attachments/cipher-attachments.component.ts | 1 - .../delete-attachment/delete-attachment.component.ts | 1 - .../components/autofill-options/autofill-options.component.ts | 1 - .../components/autofill-options/uri-option.component.ts | 1 - .../card-details-section/card-details-section.component.ts | 1 - libs/vault/src/cipher-form/components/cipher-form.component.ts | 1 - .../cipher-generator/cipher-form-generator.component.spec.ts | 2 -- .../cipher-generator/cipher-form-generator.component.ts | 1 - .../add-edit-custom-field-dialog.component.ts | 1 - .../components/custom-fields/custom-fields.component.ts | 1 - .../src/cipher-form/components/identity/identity.component.ts | 1 - .../components/item-details/item-details-section.component.ts | 1 - .../login-details-section.component.spec.ts | 1 - .../login-details-section/login-details-section.component.ts | 1 - .../components/new-item-nudge/new-item-nudge.component.ts | 1 - .../components/sshkey-section/sshkey-section.component.ts | 1 - .../additional-options/additional-options.component.ts | 1 - .../cipher-view/attachments/attachments-v2-view.component.ts | 1 - .../src/cipher-view/attachments/attachments-v2.component.ts | 1 - .../autofill-options/autofill-options-view.component.ts | 1 - .../cipher-view/card-details/card-details-view.component.ts | 1 - libs/vault/src/cipher-view/cipher-view.component.ts | 1 - .../cipher-view/custom-fields/custom-fields-v2.component.ts | 1 - .../src/cipher-view/item-details/item-details-v2.component.ts | 1 - .../src/cipher-view/item-history/item-history-v2.component.ts | 1 - .../login-credentials/login-credentials-view.component.ts | 1 - .../read-only-cipher-card/read-only-cipher-card.component.ts | 1 - .../src/cipher-view/sshkey-sections/sshkey-view.component.ts | 1 - .../view-identity-sections/view-identity-sections.component.ts | 1 - .../add-edit-folder-dialog/add-edit-folder-dialog.component.ts | 1 - libs/vault/src/components/assign-collections.component.ts | 1 - libs/vault/src/components/can-delete-cipher.directive.ts | 1 - .../carousel/carousel-button/carousel-button.component.ts | 1 - .../carousel-content/carousel-content.component.spec.ts | 1 - .../carousel/carousel-content/carousel-content.component.ts | 1 - .../carousel/carousel-slide/carousel-slide.component.spec.ts | 1 - .../carousel/carousel-slide/carousel-slide.component.ts | 1 - libs/vault/src/components/carousel/carousel.component.spec.ts | 1 - libs/vault/src/components/carousel/carousel.component.ts | 1 - libs/vault/src/components/copy-cipher-field.directive.ts | 1 - libs/vault/src/components/dark-image-source.directive.ts | 1 - .../decryption-failure-dialog.component.ts | 1 - .../download-attachment/download-attachment.component.ts | 1 - libs/vault/src/components/org-icon.directive.ts | 1 - .../password-history-view/password-history-view.component.ts | 1 - .../components/password-history/password-history.component.ts | 1 - libs/vault/src/components/password-reprompt.component.ts | 1 - .../src/components/totp-countdown/totp-countdown.component.ts | 1 - 101 files changed, 107 deletions(-) diff --git a/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.ts b/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.ts index ed78d9433f1..18482706272 100644 --- a/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.ts +++ b/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.ts @@ -11,7 +11,6 @@ import { I18nPipe } from "@bitwarden/ui-common"; @Component({ selector: "vault-at-risk-password-callout", - standalone: true, imports: [CommonModule, AnchorLinkDirective, RouterModule, CalloutModule, I18nPipe], templateUrl: "./at-risk-password-callout.component.html", }) diff --git a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts index 0133bccd25c..4f6a682e58d 100644 --- a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts +++ b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts @@ -27,7 +27,6 @@ export enum AtRiskCarouselDialogResult { DarkImageSourceDirective, I18nPipe, ], - standalone: true, }) export class AtRiskCarouselDialogComponent { private dialogRef = inject(DialogRef); diff --git a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.spec.ts b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.spec.ts index ff583061684..dae00ba6c2b 100644 --- a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.spec.ts +++ b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.spec.ts @@ -35,7 +35,6 @@ import { AtRiskPasswordPageService } from "./at-risk-password-page.service"; import { AtRiskPasswordsComponent } from "./at-risk-passwords.component"; @Component({ - standalone: true, selector: "popup-header", template: `<ng-content></ng-content>`, }) @@ -45,7 +44,6 @@ class MockPopupHeaderComponent { } @Component({ - standalone: true, selector: "popup-page", template: `<ng-content></ng-content>`, }) @@ -54,7 +52,6 @@ class MockPopupPageComponent { } @Component({ - standalone: true, selector: "app-vault-icon", template: `<ng-content></ng-content>`, }) diff --git a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.ts b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.ts index 1b43151193a..dc6712aa23f 100644 --- a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.ts +++ b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.ts @@ -79,7 +79,6 @@ import { AtRiskPasswordPageService } from "./at-risk-password-page.service"; { provide: ChangeLoginPasswordService, useClass: DefaultChangeLoginPasswordService }, ], selector: "vault-at-risk-passwords", - standalone: true, templateUrl: "./at-risk-passwords.component.html", }) export class AtRiskPasswordsComponent implements OnInit { diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts index e47f4637199..a5a6a6f9922 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts @@ -132,7 +132,6 @@ export type AddEditQueryParams = Partial<Record<keyof QueryParams, string>>; @Component({ selector: "app-add-edit-v2", templateUrl: "add-edit-v2.component.html", - standalone: true, providers: [ { provide: CipherFormConfigService, useClass: DefaultCipherFormConfigService }, { provide: TotpCaptureService, useClass: BrowserTotpCaptureService }, diff --git a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts index a11a7d806bd..8374cc254a9 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts @@ -28,7 +28,6 @@ import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component"; @Component({ - standalone: true, selector: "app-assign-collections", templateUrl: "./assign-collections.component.html", imports: [ diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts index 7c2cc99e300..6e4215c1ec2 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts @@ -26,7 +26,6 @@ import { PopupRouterCacheService } from "../../../../../platform/popup/view-cach import { AttachmentsV2Component } from "./attachments-v2.component"; @Component({ - standalone: true, selector: "popup-header", template: `<ng-content></ng-content>`, }) @@ -36,7 +35,6 @@ class MockPopupHeaderComponent { } @Component({ - standalone: true, selector: "popup-footer", template: `<ng-content></ng-content>`, }) diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts index 32d446daf75..fc6d882dfd5 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts @@ -18,7 +18,6 @@ import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-p import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; @Component({ - standalone: true, selector: "app-attachments-v2", templateUrl: "./attachments-v2.component.html", imports: [ diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts index d44b54bcb96..6577975ae0c 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts @@ -24,7 +24,6 @@ import BrowserPopupUtils from "../../../../../../platform/popup/browser-popup-ut import { FilePopoutUtilsService } from "../../../../../../tools/popup/services/file-popout-utils.service"; @Component({ - standalone: true, selector: "app-open-attachments", templateUrl: "./open-attachments.component.html", imports: [BadgeModule, CommonModule, ItemModule, JslibModule, TypographyModule], diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts index bdc0d7ae5bc..b490d71df83 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts @@ -15,7 +15,6 @@ import { PopupCipherView } from "../../../views/popup-cipher.view"; import { VaultListItemsContainerComponent } from "../vault-list-items-container/vault-list-items-container.component"; @Component({ - standalone: true, imports: [ CommonModule, TypographyModule, diff --git a/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts index 3a17825f4fb..5824e8d97ea 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts @@ -16,7 +16,6 @@ import { VaultPopupAutofillService } from "../../../services/vault-popup-autofil const blockedURISettingsRoute = "/blocked-domains"; @Component({ - standalone: true, imports: [ BannerModule, CommonModule, diff --git a/apps/browser/src/vault/popup/components/vault-v2/intro-carousel/intro-carousel.component.ts b/apps/browser/src/vault/popup/components/vault-v2/intro-carousel/intro-carousel.component.ts index 96e1a70306e..527f0f246af 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/intro-carousel/intro-carousel.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/intro-carousel/intro-carousel.component.ts @@ -20,7 +20,6 @@ import { IntroCarouselService } from "../../../services/intro-carousel.service"; JslibModule, I18nPipe, ], - standalone: true, }) export class IntroCarouselComponent { protected securityHandshake = VaultIcons.SecurityHandshake; diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.ts index 053d87c6485..de548e7be7a 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.ts @@ -19,7 +19,6 @@ type CipherItem = { }; @Component({ - standalone: true, selector: "app-item-copy-actions", templateUrl: "item-copy-actions.component.html", imports: [ diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts index 94b4c2b855b..bb7b74f8c61 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts @@ -28,7 +28,6 @@ import { VaultPopupAutofillService } from "../../../services/vault-popup-autofil import { AddEditQueryParams } from "../add-edit/add-edit-v2.component"; @Component({ - standalone: true, selector: "app-item-more-options", templateUrl: "./item-more-options.component.html", imports: [ItemModule, IconButtonModule, MenuModule, CommonModule, JslibModule, RouterModule], diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts index 1bcc4297b71..ef0b009025d 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts @@ -23,7 +23,6 @@ export interface NewItemInitialValues { @Component({ selector: "app-new-item-dropdown", templateUrl: "new-item-dropdown-v2.component.html", - standalone: true, imports: [NoItemsModule, JslibModule, CommonModule, ButtonModule, RouterLink, MenuModule], }) export class NewItemDropdownV2Component implements OnInit { diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts index dd5f55a66ee..b5d35e2005e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts @@ -21,7 +21,6 @@ import { @Component({ selector: "vault-cipher-form-generator", template: "", - standalone: true, }) class MockCipherFormGenerator { @Input() type: "password" | "username" = "password"; diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts index 4daffa6a9b8..f02ce46e931 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts @@ -40,7 +40,6 @@ export enum GeneratorDialogAction { @Component({ selector: "app-vault-generator-dialog", templateUrl: "./vault-generator-dialog.component.html", - standalone: true, imports: [ PopupPageComponent, PopupHeaderComponent, diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts index bcea2e76190..f64b5e6b83d 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts @@ -20,7 +20,6 @@ import { VaultV2SearchComponent } from "../vault-search/vault-v2-search.componen @Component({ selector: "app-vault-header-v2", templateUrl: "vault-header-v2.component.html", - standalone: true, imports: [ VaultV2SearchComponent, VaultListFiltersComponent, diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.ts index feccf92cec2..bc43a1d6a46 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.ts @@ -9,7 +9,6 @@ import { ChipSelectComponent } from "@bitwarden/components"; import { VaultPopupListFiltersService } from "../../../services/vault-popup-list-filters.service"; @Component({ - standalone: true, selector: "app-vault-list-filters", templateUrl: "./vault-list-filters.component.html", imports: [CommonModule, JslibModule, ChipSelectComponent, ReactiveFormsModule], diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts index 073d49333b5..cef1ef8d2ff 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts @@ -77,7 +77,6 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options ], selector: "app-vault-list-items-container", templateUrl: "vault-list-items-container.component.html", - standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, }) export class VaultListItemsContainerComponent implements OnInit, AfterViewInit { diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts index d0eef20f044..f2764df7ba7 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts @@ -19,7 +19,6 @@ import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-p import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; @Component({ - standalone: true, selector: "vault-password-history-v2", templateUrl: "vault-password-history-v2.component.html", imports: [ diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts index b68818454d1..fe2baf463cf 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts @@ -13,7 +13,6 @@ const SearchTextDebounceInterval = 200; @Component({ imports: [CommonModule, SearchModule, JslibModule, FormsModule], - standalone: true, selector: "app-vault-v2-search", templateUrl: "vault-v2-search.component.html", }) diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index db853d45940..9310953dbb7 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -66,7 +66,6 @@ enum VaultState { @Component({ selector: "app-vault", templateUrl: "vault-v2.component.html", - standalone: true, imports: [ BlockedInjectionBanner, PopupPageComponent, diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index 0a71caf5aee..77b1819e29d 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -80,7 +80,6 @@ type LoadAction = @Component({ selector: "app-view-v2", templateUrl: "view-v2.component.html", - standalone: true, imports: [ CommonModule, SearchModule, diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts b/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts index 30715ebaedf..738ec3ae1ff 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.spec.ts @@ -23,7 +23,6 @@ import { VaultPopupCopyButtonsService } from "../services/vault-popup-copy-butto import { AppearanceV2Component } from "./appearance-v2.component"; @Component({ - standalone: true, selector: "popup-header", template: `<ng-content></ng-content>`, }) @@ -33,7 +32,6 @@ class MockPopupHeaderComponent { } @Component({ - standalone: true, selector: "popup-page", template: `<ng-content></ng-content>`, }) diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts index 1462a2d7ab4..2a38d281396 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.ts @@ -35,7 +35,6 @@ import { import { VaultPopupCopyButtonsService } from "../services/vault-popup-copy-buttons.service"; @Component({ - standalone: true, templateUrl: "./appearance-v2.component.html", imports: [ CommonModule, diff --git a/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts b/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts index 0287b7d504f..d23d00a1ad7 100644 --- a/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts +++ b/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts @@ -15,7 +15,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co @Component({ templateUrl: "download-bitwarden.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts b/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts index 6689f5a6c6d..d1450667fa8 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts @@ -22,7 +22,6 @@ import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-heade import { FoldersV2Component } from "./folders-v2.component"; @Component({ - standalone: true, selector: "popup-header", template: `<ng-content></ng-content>`, }) @@ -32,7 +31,6 @@ class MockPopupHeaderComponent { } @Component({ - standalone: true, selector: "popup-footer", template: `<ng-content></ng-content>`, }) diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.ts b/apps/browser/src/vault/popup/settings/folders-v2.component.ts index f71374e5305..2264415f4fa 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.ts @@ -22,7 +22,6 @@ import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-heade import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @Component({ - standalone: true, templateUrl: "./folders-v2.component.html", imports: [ CommonModule, diff --git a/apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.ts b/apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.ts index b1269963f70..ec7a73a3bc3 100644 --- a/apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.ts @@ -19,7 +19,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co @Component({ templateUrl: "more-from-bitwarden-page-v2.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts index b4899e12eda..0f025ebe3f4 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts @@ -37,7 +37,6 @@ import { PopupCipherView } from "../../views/popup-cipher.view"; @Component({ selector: "app-trash-list-items-container", templateUrl: "trash-list-items-container.component.html", - standalone: true, imports: [ CommonModule, ItemModule, diff --git a/apps/browser/src/vault/popup/settings/trash.component.ts b/apps/browser/src/vault/popup/settings/trash.component.ts index 61843de31bc..d6e5f899bba 100644 --- a/apps/browser/src/vault/popup/settings/trash.component.ts +++ b/apps/browser/src/vault/popup/settings/trash.component.ts @@ -14,7 +14,6 @@ import { TrashListItemsContainerComponent } from "./trash-list-items-container/t @Component({ templateUrl: "trash.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts index 049e853d2cd..9efdc568997 100644 --- a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts @@ -15,7 +15,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co @Component({ templateUrl: "vault-settings-v2.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts index 204615443ba..1a375fc0f5a 100644 --- a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts +++ b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts @@ -39,7 +39,6 @@ export enum CredentialGeneratorDialogAction { } @Component({ - standalone: true, selector: "credential-generator-dialog", templateUrl: "credential-generator-dialog.component.html", imports: [ diff --git a/apps/desktop/src/vault/app/vault/item-footer.component.ts b/apps/desktop/src/vault/app/vault/item-footer.component.ts index 639d1557ecd..a022246ed94 100644 --- a/apps/desktop/src/vault/app/vault/item-footer.component.ts +++ b/apps/desktop/src/vault/app/vault/item-footer.component.ts @@ -18,7 +18,6 @@ import { PasswordRepromptService } from "@bitwarden/vault"; @Component({ selector: "app-vault-item-footer", templateUrl: "item-footer.component.html", - standalone: true, imports: [ButtonModule, CommonModule, JslibModule], }) export class ItemFooterComponent implements OnInit { diff --git a/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts index 31d4098d2b2..5a832ed79b0 100644 --- a/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts @@ -17,7 +17,6 @@ import { SearchBarService } from "../../../app/layout/search/search-bar.service" @Component({ selector: "app-vault-items-v2", templateUrl: "vault-items-v2.component.html", - standalone: true, imports: [MenuModule, CommonModule, JslibModule, ScrollingModule], }) export class VaultItemsV2Component extends BaseVaultItemsComponent { diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index b45d943dcdd..70f0f29deee 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -79,7 +79,6 @@ const BroadcasterSubscriptionId = "VaultComponent"; @Component({ selector: "app-vault", templateUrl: "vault-v2.component.html", - standalone: true, imports: [ BadgeModule, CommonModule, diff --git a/apps/web/src/app/vault/components/assign-collections/assign-collections-web.component.ts b/apps/web/src/app/vault/components/assign-collections/assign-collections-web.component.ts index 716d0b5a2bf..753d2708e60 100644 --- a/apps/web/src/app/vault/components/assign-collections/assign-collections-web.component.ts +++ b/apps/web/src/app/vault/components/assign-collections/assign-collections-web.component.ts @@ -15,7 +15,6 @@ import { SharedModule } from "../../../shared"; @Component({ imports: [SharedModule, AssignCollectionsComponent, PluralizePipe], templateUrl: "./assign-collections-web.component.html", - standalone: true, }) export class AssignCollectionsWebComponent { protected editableItemCount: number; diff --git a/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt-install.component.ts b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt-install.component.ts index 73f4307d9cc..005fbb1b14d 100644 --- a/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt-install.component.ts +++ b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt-install.component.ts @@ -28,7 +28,6 @@ const WebStoreUrls: Partial<Record<DeviceType, string>> = { @Component({ selector: "vault-browser-extension-prompt-install", templateUrl: "./browser-extension-prompt-install.component.html", - standalone: true, imports: [CommonModule, I18nPipe, LinkModule], }) export class BrowserExtensionPromptInstallComponent implements OnInit { diff --git a/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.ts b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.ts index 9800d0c64fe..624275a8297 100644 --- a/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.ts +++ b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.ts @@ -13,7 +13,6 @@ import { @Component({ selector: "vault-browser-extension-prompt", templateUrl: "./browser-extension-prompt.component.html", - standalone: true, imports: [CommonModule, I18nPipe, ButtonComponent, IconModule], }) export class BrowserExtensionPromptComponent implements OnInit, OnDestroy { diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index aa457e97093..11c326d72df 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -122,7 +122,6 @@ export enum VaultItemDialogResult { @Component({ selector: "app-vault-item-dialog", templateUrl: "vault-item-dialog.component.html", - standalone: true, imports: [ ButtonModule, CipherViewComponent, diff --git a/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.spec.ts b/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.spec.ts index 806cb51bfce..085a3d0d4b0 100644 --- a/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.spec.ts +++ b/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.spec.ts @@ -19,7 +19,6 @@ import { @Component({ selector: "vault-cipher-form-generator", template: "", - standalone: true, }) class MockCipherFormGenerator { @Input() type: "password" | "username" = "password"; diff --git a/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts b/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts index e20efa9dbb8..8ff0709a5ed 100644 --- a/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts +++ b/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts @@ -36,7 +36,6 @@ export enum WebVaultGeneratorDialogAction { @Component({ selector: "web-vault-generator-dialog", templateUrl: "./web-generator-dialog.component.html", - standalone: true, imports: [CommonModule, CipherFormGeneratorComponent, ButtonModule, DialogModule, I18nPipe], }) export class WebVaultGeneratorDialogComponent { diff --git a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts index 621e0ec88c5..76b7cd3723b 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts @@ -64,7 +64,6 @@ export interface AddEditCipherDialogCloseResult { @Component({ selector: "app-vault-add-edit-v2", templateUrl: "add-edit-v2.component.html", - standalone: true, imports: [ CommonModule, AsyncActionsModule, diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts index 5f5fc1e218d..22a4f5f8c92 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts @@ -16,7 +16,6 @@ import { SharedModule } from "../../../shared"; import { VaultBannersService, VisibleVaultBanner } from "./services/vault-banners.service"; @Component({ - standalone: true, selector: "app-vault-banners", templateUrl: "./vault-banners.component.html", imports: [VerifyEmailComponent, SharedModule, BannerModule], diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts index 1049ee17faf..5466bbb6137 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts @@ -42,7 +42,6 @@ import { } from "../vault-filter/shared/models/routed-vault-filter.model"; @Component({ - standalone: true, selector: "app-vault-header", templateUrl: "./vault-header.component.html", imports: [ diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts index b3a4b324d30..a4026b7d355 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts @@ -33,7 +33,6 @@ import { VaultOnboardingService as VaultOnboardingServiceAbstraction } from "./s import { VaultOnboardingService, VaultOnboardingTasks } from "./services/vault-onboarding.service"; @Component({ - standalone: true, imports: [OnboardingModule, CommonModule, JslibModule, LinkModule], providers: [ { diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 0dfaa1ac589..26fcb7a8b04 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -133,7 +133,6 @@ const BroadcasterSubscriptionId = "VaultComponent"; const SearchTextDebounceInterval = 200; @Component({ - standalone: true, selector: "app-vault", templateUrl: "vault.component.html", imports: [ diff --git a/apps/web/src/app/vault/individual-vault/view.component.ts b/apps/web/src/app/vault/individual-vault/view.component.ts index f52a4da3ffb..15a1a46b107 100644 --- a/apps/web/src/app/vault/individual-vault/view.component.ts +++ b/apps/web/src/app/vault/individual-vault/view.component.ts @@ -73,7 +73,6 @@ export interface ViewCipherDialogCloseResult { @Component({ selector: "app-vault-view", templateUrl: "view.component.html", - standalone: true, imports: [CipherViewComponent, CommonModule, AsyncActionsModule, DialogModule, SharedModule], providers: [ { provide: ViewPasswordHistoryService, useClass: VaultViewPasswordHistoryService }, diff --git a/libs/angular/src/vault/components/spotlight/spotlight.component.ts b/libs/angular/src/vault/components/spotlight/spotlight.component.ts index a2e2a13a468..3c64318a900 100644 --- a/libs/angular/src/vault/components/spotlight/spotlight.component.ts +++ b/libs/angular/src/vault/components/spotlight/spotlight.component.ts @@ -7,7 +7,6 @@ import { I18nPipe } from "@bitwarden/ui-common"; @Component({ selector: "bit-spotlight", templateUrl: "spotlight.component.html", - standalone: true, imports: [ButtonModule, CommonModule, IconButtonModule, I18nPipe, TypographyModule], }) export class SpotlightComponent { diff --git a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts index f1c8085ae15..5b93a7bdefe 100644 --- a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts @@ -14,7 +14,6 @@ import { CustomFieldsComponent } from "../custom-fields/custom-fields.component" import { AdditionalOptionsSectionComponent } from "./additional-options-section.component"; @Component({ - standalone: true, selector: "vault-custom-fields", template: "", }) diff --git a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.ts b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.ts index 14f3494652a..7877144f9f0 100644 --- a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.ts +++ b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.ts @@ -24,7 +24,6 @@ import { CustomFieldsComponent } from "../custom-fields/custom-fields.component" @Component({ selector: "vault-additional-options-section", templateUrl: "./additional-options-section.component.html", - standalone: true, imports: [ CommonModule, SectionHeaderComponent, diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts index da827addf67..439c651e5ad 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts @@ -27,7 +27,6 @@ import { CipherAttachmentsComponent } from "./cipher-attachments.component"; import { DeleteAttachmentComponent } from "./delete-attachment/delete-attachment.component"; @Component({ - standalone: true, selector: "app-download-attachment", template: "", }) diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts index aa9769ec392..0bcb31c7af9 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts @@ -57,7 +57,6 @@ type CipherAttachmentForm = FormGroup<{ }>; @Component({ - standalone: true, selector: "app-cipher-attachments", templateUrl: "./cipher-attachments.component.html", imports: [ diff --git a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts index ce7a5a22dd8..be6f10c01de 100644 --- a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts @@ -18,7 +18,6 @@ import { } from "@bitwarden/components"; @Component({ - standalone: true, selector: "app-delete-attachment", templateUrl: "./delete-attachment.component.html", imports: [AsyncActionsModule, CommonModule, JslibModule, ButtonModule, IconButtonModule], diff --git a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts index ccbc792648e..b328c0ddd72 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts +++ b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts @@ -39,7 +39,6 @@ interface UriField { @Component({ selector: "vault-autofill-options", templateUrl: "./autofill-options.component.html", - standalone: true, imports: [ DragDropModule, SectionHeaderComponent, diff --git a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts index 07bf7bef775..3f12382b931 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts +++ b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts @@ -35,7 +35,6 @@ import { @Component({ selector: "vault-autofill-uri-option", templateUrl: "./uri-option.component.html", - standalone: true, providers: [ { provide: NG_VALUE_ACCESSOR, diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts index 8086d2bf0c4..a71f57481ff 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts @@ -26,7 +26,6 @@ import { CipherFormContainer } from "../../cipher-form-container"; @Component({ selector: "vault-card-details-section", templateUrl: "./card-details-section.component.html", - standalone: true, imports: [ CardComponent, TypographyModule, diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.ts b/libs/vault/src/cipher-form/components/cipher-form.component.ts index 08dc71c9886..b8815235ee8 100644 --- a/libs/vault/src/cipher-form/components/cipher-form.component.ts +++ b/libs/vault/src/cipher-form/components/cipher-form.component.ts @@ -49,7 +49,6 @@ import { SshKeySectionComponent } from "./sshkey-section/sshkey-section.componen @Component({ selector: "vault-cipher-form", templateUrl: "./cipher-form.component.html", - standalone: true, providers: [ { provide: CipherFormContainer, diff --git a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.spec.ts b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.spec.ts index 7949c1fcc11..e98e4805d19 100644 --- a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.spec.ts +++ b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.spec.ts @@ -9,7 +9,6 @@ import { CipherFormGeneratorComponent } from "@bitwarden/vault"; @Component({ selector: "tools-password-generator", template: `<ng-content></ng-content>`, - standalone: true, }) class MockPasswordGeneratorComponent { @Output() onGenerated = new EventEmitter(); @@ -18,7 +17,6 @@ class MockPasswordGeneratorComponent { @Component({ selector: "tools-username-generator", template: `<ng-content></ng-content>`, - standalone: true, }) class MockUsernameGeneratorComponent { @Output() onGenerated = new EventEmitter(); diff --git a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts index b9e5ed3c0ab..f1e4c5c177c 100644 --- a/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts +++ b/libs/vault/src/cipher-form/components/cipher-generator/cipher-form-generator.component.ts @@ -12,7 +12,6 @@ import { AlgorithmInfo, GeneratedCredential } from "@bitwarden/generator-core"; @Component({ selector: "vault-cipher-form-generator", templateUrl: "./cipher-form-generator.component.html", - standalone: true, imports: [CommonModule, GeneratorModule], }) export class CipherFormGeneratorComponent { diff --git a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts index 72bdf5dca1a..7ddcf902d70 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.ts @@ -29,7 +29,6 @@ export type AddEditCustomFieldDialogData = { }; @Component({ - standalone: true, selector: "vault-add-edit-custom-field-dialog", templateUrl: "./add-edit-custom-field-dialog.component.html", imports: [ diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts index 5d43f52788a..52cb740ad03 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts @@ -69,7 +69,6 @@ export type CustomField = { }; @Component({ - standalone: true, selector: "vault-custom-fields", templateUrl: "./custom-fields.component.html", imports: [ diff --git a/libs/vault/src/cipher-form/components/identity/identity.component.ts b/libs/vault/src/cipher-form/components/identity/identity.component.ts index 3cc8e73697f..119ce1caf6e 100644 --- a/libs/vault/src/cipher-form/components/identity/identity.component.ts +++ b/libs/vault/src/cipher-form/components/identity/identity.component.ts @@ -22,7 +22,6 @@ import { import { CipherFormContainer } from "../../cipher-form-container"; @Component({ - standalone: true, selector: "vault-identity-section", templateUrl: "./identity.component.html", imports: [ diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts index a1cf33c449b..192fac44a2d 100644 --- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts @@ -36,7 +36,6 @@ import { CipherFormContainer } from "../../cipher-form-container"; @Component({ selector: "vault-item-details-section", templateUrl: "./item-details-section.component.html", - standalone: true, imports: [ CardComponent, TypographyModule, diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts index e0ba52a54a2..c5b1fc7897b 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.spec.ts @@ -23,7 +23,6 @@ import { AutofillOptionsComponent } from "../autofill-options/autofill-options.c import { LoginDetailsSectionComponent } from "./login-details-section.component"; @Component({ - standalone: true, selector: "vault-autofill-options", template: "", }) diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts index c6b8433dcfa..e74d9915cdb 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.ts @@ -33,7 +33,6 @@ import { AutofillOptionsComponent } from "../autofill-options/autofill-options.c @Component({ selector: "vault-login-details-section", templateUrl: "./login-details-section.component.html", - standalone: true, imports: [ ReactiveFormsModule, SectionHeaderComponent, diff --git a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts index 705b98f241a..79defc271cf 100644 --- a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts +++ b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts @@ -13,7 +13,6 @@ import { CipherType } from "@bitwarden/sdk-internal"; @Component({ selector: "vault-new-item-nudge", templateUrl: "./new-item-nudge.component.html", - standalone: true, imports: [NgIf, SpotlightComponent], }) export class NewItemNudgeComponent implements OnInit { diff --git a/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts index fcf2ba0d9f7..71f85e0e1b3 100644 --- a/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts +++ b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts @@ -28,7 +28,6 @@ import { CipherFormContainer } from "../../cipher-form-container"; @Component({ selector: "vault-sshkey-section", templateUrl: "./sshkey-section.component.html", - standalone: true, imports: [ CardComponent, TypographyModule, diff --git a/libs/vault/src/cipher-view/additional-options/additional-options.component.ts b/libs/vault/src/cipher-view/additional-options/additional-options.component.ts index 0f2c99800f8..3e632983d49 100644 --- a/libs/vault/src/cipher-view/additional-options/additional-options.component.ts +++ b/libs/vault/src/cipher-view/additional-options/additional-options.component.ts @@ -14,7 +14,6 @@ import { @Component({ selector: "app-additional-options", templateUrl: "additional-options.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts index 04e69fcccd6..711c63878e3 100644 --- a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts +++ b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts @@ -25,7 +25,6 @@ import { DownloadAttachmentComponent } from "../../components/download-attachmen @Component({ selector: "app-attachments-v2-view", templateUrl: "attachments-v2-view.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts b/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts index b34d0d3a312..7eb3d371292 100644 --- a/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts +++ b/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts @@ -42,7 +42,6 @@ export interface AttachmentDialogCloseResult { @Component({ selector: "app-vault-attachments-v2", templateUrl: "attachments-v2.component.html", - standalone: true, imports: [ButtonModule, CommonModule, DialogModule, I18nPipe, CipherAttachmentsComponent], }) export class AttachmentsV2Component { diff --git a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts index bab37324993..0643737d846 100644 --- a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts +++ b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts @@ -21,7 +21,6 @@ import { @Component({ selector: "app-autofill-options-view", templateUrl: "autofill-options-view.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/vault/src/cipher-view/card-details/card-details-view.component.ts b/libs/vault/src/cipher-view/card-details/card-details-view.component.ts index 2d1b2800c79..502214848f3 100644 --- a/libs/vault/src/cipher-view/card-details/card-details-view.component.ts +++ b/libs/vault/src/cipher-view/card-details/card-details-view.component.ts @@ -20,7 +20,6 @@ import { ReadOnlyCipherCardComponent } from "../read-only-cipher-card/read-only- @Component({ selector: "app-card-details-view", templateUrl: "card-details-view.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index 02968d6d149..66910ad8ac7 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -41,7 +41,6 @@ import { ViewIdentitySectionsComponent } from "./view-identity-sections/view-ide @Component({ selector: "app-cipher-view", templateUrl: "cipher-view.component.html", - standalone: true, imports: [ CalloutModule, CommonModule, diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts index 4d20eceb285..9a6f93026e5 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts @@ -28,7 +28,6 @@ import { VaultAutosizeReadOnlyTextArea } from "../../directives/readonly-textare @Component({ selector: "app-custom-fields-v2", templateUrl: "custom-fields-v2.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts b/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts index fbedcbb54df..8f0fedbe599 100644 --- a/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts +++ b/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts @@ -22,7 +22,6 @@ import { OrgIconDirective } from "../../components/org-icon.directive"; @Component({ selector: "app-item-details-v2", templateUrl: "item-details-v2.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts b/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts index f49d7030d77..2bbb6418934 100644 --- a/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts +++ b/libs/vault/src/cipher-view/item-history/item-history-v2.component.ts @@ -19,7 +19,6 @@ import { @Component({ selector: "app-item-history-v2", templateUrl: "item-history-v2.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts index 5f7d0b32201..5eeb3f9e8f3 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts @@ -42,7 +42,6 @@ type TotpCodeValues = { @Component({ selector: "app-login-credentials-view", templateUrl: "login-credentials-view.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts b/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts index 9005ea9674c..8f6b9954a9f 100644 --- a/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts +++ b/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts @@ -5,7 +5,6 @@ import { CardComponent, BitFormFieldComponent } from "@bitwarden/components"; @Component({ selector: "read-only-cipher-card", templateUrl: "./read-only-cipher-card.component.html", - standalone: true, imports: [CardComponent], }) /** diff --git a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts index e0ec1358ee1..5bce5527112 100644 --- a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts +++ b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts @@ -17,7 +17,6 @@ import { ReadOnlyCipherCardComponent } from "../read-only-cipher-card/read-only- @Component({ selector: "app-sshkey-view", templateUrl: "sshkey-view.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts index 3b710812b36..f9cb9d2b549 100644 --- a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts +++ b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts @@ -13,7 +13,6 @@ import { import { ReadOnlyCipherCardComponent } from "../read-only-cipher-card/read-only-cipher-card.component"; @Component({ - standalone: true, selector: "app-view-identity-sections", templateUrl: "./view-identity-sections.component.html", imports: [ diff --git a/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.ts b/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.ts index dd3cbc4c5c9..bb79c7877a9 100644 --- a/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.ts +++ b/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.ts @@ -47,7 +47,6 @@ export type AddEditFolderDialogData = { }; @Component({ - standalone: true, selector: "vault-add-edit-folder-dialog", templateUrl: "./add-edit-folder-dialog.component.html", imports: [ diff --git a/libs/vault/src/components/assign-collections.component.ts b/libs/vault/src/components/assign-collections.component.ts index 4a0bd1fc670..42e6d9c92c5 100644 --- a/libs/vault/src/components/assign-collections.component.ts +++ b/libs/vault/src/components/assign-collections.component.ts @@ -94,7 +94,6 @@ const MY_VAULT_ID = "MyVault"; @Component({ selector: "assign-collections", templateUrl: "assign-collections.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/vault/src/components/can-delete-cipher.directive.ts b/libs/vault/src/components/can-delete-cipher.directive.ts index c1c7706a1fa..7eadedc7ada 100644 --- a/libs/vault/src/components/can-delete-cipher.directive.ts +++ b/libs/vault/src/components/can-delete-cipher.directive.ts @@ -9,7 +9,6 @@ import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cip */ @Directive({ selector: "[appCanDeleteCipher]", - standalone: true, }) export class CanDeleteCipherDirective implements OnDestroy { private destroy$ = new Subject<void>(); diff --git a/libs/vault/src/components/carousel/carousel-button/carousel-button.component.ts b/libs/vault/src/components/carousel/carousel-button/carousel-button.component.ts index d0e353dcc76..7b5f7d3b164 100644 --- a/libs/vault/src/components/carousel/carousel-button/carousel-button.component.ts +++ b/libs/vault/src/components/carousel/carousel-button/carousel-button.component.ts @@ -10,7 +10,6 @@ import { VaultCarouselSlideComponent } from "../carousel-slide/carousel-slide.co @Component({ selector: "vault-carousel-button", templateUrl: "carousel-button.component.html", - standalone: true, imports: [CommonModule, IconModule], }) export class VaultCarouselButtonComponent implements FocusableOption { diff --git a/libs/vault/src/components/carousel/carousel-content/carousel-content.component.spec.ts b/libs/vault/src/components/carousel/carousel-content/carousel-content.component.spec.ts index 2900225b886..bc1c9250c2c 100644 --- a/libs/vault/src/components/carousel/carousel-content/carousel-content.component.spec.ts +++ b/libs/vault/src/components/carousel/carousel-content/carousel-content.component.spec.ts @@ -7,7 +7,6 @@ import { VaultCarouselContentComponent } from "./carousel-content.component"; @Component({ selector: "app-test-template-ref", - standalone: true, imports: [VaultCarouselContentComponent], template: ` <ng-template #template> diff --git a/libs/vault/src/components/carousel/carousel-content/carousel-content.component.ts b/libs/vault/src/components/carousel/carousel-content/carousel-content.component.ts index 7051a8ff8fa..47027a77ae9 100644 --- a/libs/vault/src/components/carousel/carousel-content/carousel-content.component.ts +++ b/libs/vault/src/components/carousel/carousel-content/carousel-content.component.ts @@ -4,7 +4,6 @@ import { Component, Input } from "@angular/core"; @Component({ selector: "vault-carousel-content", templateUrl: "carousel-content.component.html", - standalone: true, imports: [CdkPortalOutlet], }) export class VaultCarouselContentComponent { diff --git a/libs/vault/src/components/carousel/carousel-slide/carousel-slide.component.spec.ts b/libs/vault/src/components/carousel/carousel-slide/carousel-slide.component.spec.ts index e69a2a2d758..46f06f6dcb4 100644 --- a/libs/vault/src/components/carousel/carousel-slide/carousel-slide.component.spec.ts +++ b/libs/vault/src/components/carousel/carousel-slide/carousel-slide.component.spec.ts @@ -7,7 +7,6 @@ import { VaultCarouselSlideComponent } from "./carousel-slide.component"; @Component({ selector: "app-test-carousel-slide", - standalone: true, imports: [VaultCarouselSlideComponent], template: ` <vault-carousel-slide><p>Carousel Slide Content!</p></vault-carousel-slide> `, }) diff --git a/libs/vault/src/components/carousel/carousel-slide/carousel-slide.component.ts b/libs/vault/src/components/carousel/carousel-slide/carousel-slide.component.ts index a516914f0c0..811572881da 100644 --- a/libs/vault/src/components/carousel/carousel-slide/carousel-slide.component.ts +++ b/libs/vault/src/components/carousel/carousel-slide/carousel-slide.component.ts @@ -14,7 +14,6 @@ import { @Component({ selector: "vault-carousel-slide", templateUrl: "./carousel-slide.component.html", - standalone: true, imports: [CommonModule], }) export class VaultCarouselSlideComponent implements OnInit { diff --git a/libs/vault/src/components/carousel/carousel.component.spec.ts b/libs/vault/src/components/carousel/carousel.component.spec.ts index 500b8115bad..1409aea0cb2 100644 --- a/libs/vault/src/components/carousel/carousel.component.spec.ts +++ b/libs/vault/src/components/carousel/carousel.component.spec.ts @@ -7,7 +7,6 @@ import { VaultCarouselComponent } from "./carousel.component"; @Component({ selector: "app-test-carousel-slide", - standalone: true, imports: [VaultCarouselComponent, VaultCarouselSlideComponent], template: ` <vault-carousel label="Storybook Demo"> diff --git a/libs/vault/src/components/carousel/carousel.component.ts b/libs/vault/src/components/carousel/carousel.component.ts index 2346ee29902..275b9de4fcb 100644 --- a/libs/vault/src/components/carousel/carousel.component.ts +++ b/libs/vault/src/components/carousel/carousel.component.ts @@ -25,7 +25,6 @@ import { VaultCarouselSlideComponent } from "./carousel-slide/carousel-slide.com @Component({ selector: "vault-carousel", templateUrl: "./carousel.component.html", - standalone: true, imports: [ CdkPortalOutlet, CommonModule, diff --git a/libs/vault/src/components/copy-cipher-field.directive.ts b/libs/vault/src/components/copy-cipher-field.directive.ts index 324b43f12d4..0ab7400a6dd 100644 --- a/libs/vault/src/components/copy-cipher-field.directive.ts +++ b/libs/vault/src/components/copy-cipher-field.directive.ts @@ -18,7 +18,6 @@ import { CopyAction, CopyCipherFieldService } from "@bitwarden/vault"; * ``` */ @Directive({ - standalone: true, selector: "[appCopyField]", }) export class CopyCipherFieldDirective implements OnChanges { diff --git a/libs/vault/src/components/dark-image-source.directive.ts b/libs/vault/src/components/dark-image-source.directive.ts index 9867f264365..ee54f61209a 100644 --- a/libs/vault/src/components/dark-image-source.directive.ts +++ b/libs/vault/src/components/dark-image-source.directive.ts @@ -24,7 +24,6 @@ import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-stat */ @Directive({ selector: "[appDarkImgSrc]", - standalone: true, }) export class DarkImageSourceDirective implements OnInit { private themeService = inject(ThemeStateService); diff --git a/libs/vault/src/components/decryption-failure-dialog/decryption-failure-dialog.component.ts b/libs/vault/src/components/decryption-failure-dialog/decryption-failure-dialog.component.ts index cbddcc24dc0..91b1cef364c 100644 --- a/libs/vault/src/components/decryption-failure-dialog/decryption-failure-dialog.component.ts +++ b/libs/vault/src/components/decryption-failure-dialog/decryption-failure-dialog.component.ts @@ -20,7 +20,6 @@ export type DecryptionFailureDialogParams = { }; @Component({ - standalone: true, selector: "vault-decryption-failure-dialog", templateUrl: "./decryption-failure-dialog.component.html", imports: [ diff --git a/libs/vault/src/components/download-attachment/download-attachment.component.ts b/libs/vault/src/components/download-attachment/download-attachment.component.ts index f06d6db582a..e4c0c253f4f 100644 --- a/libs/vault/src/components/download-attachment/download-attachment.component.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.ts @@ -17,7 +17,6 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { AsyncActionsModule, IconButtonModule, ToastService } from "@bitwarden/components"; @Component({ - standalone: true, selector: "app-download-attachment", templateUrl: "./download-attachment.component.html", imports: [AsyncActionsModule, CommonModule, JslibModule, IconButtonModule], diff --git a/libs/vault/src/components/org-icon.directive.ts b/libs/vault/src/components/org-icon.directive.ts index 69a19b46c65..d9c8f240474 100644 --- a/libs/vault/src/components/org-icon.directive.ts +++ b/libs/vault/src/components/org-icon.directive.ts @@ -5,7 +5,6 @@ import { ProductTierType } from "@bitwarden/common/billing/enums"; export type OrgIconSize = "default" | "small" | "large"; @Directive({ - standalone: true, selector: "[appOrgIcon]", }) export class OrgIconDirective { diff --git a/libs/vault/src/components/password-history-view/password-history-view.component.ts b/libs/vault/src/components/password-history-view/password-history-view.component.ts index 0f3c54d9d2b..427644f3e77 100644 --- a/libs/vault/src/components/password-history-view/password-history-view.component.ts +++ b/libs/vault/src/components/password-history-view/password-history-view.component.ts @@ -11,7 +11,6 @@ import { ItemModule, ColorPasswordModule, IconButtonModule } from "@bitwarden/co @Component({ selector: "vault-password-history-view", templateUrl: "./password-history-view.component.html", - standalone: true, imports: [CommonModule, ItemModule, ColorPasswordModule, IconButtonModule, JslibModule], }) export class PasswordHistoryViewComponent implements OnInit { diff --git a/libs/vault/src/components/password-history/password-history.component.ts b/libs/vault/src/components/password-history/password-history.component.ts index 5af785d1a70..7845edb2369 100644 --- a/libs/vault/src/components/password-history/password-history.component.ts +++ b/libs/vault/src/components/password-history/password-history.component.ts @@ -29,7 +29,6 @@ export interface ViewPasswordHistoryDialogParams { @Component({ selector: "app-vault-password-history", templateUrl: "password-history.component.html", - standalone: true, imports: [ ButtonModule, CommonModule, diff --git a/libs/vault/src/components/password-reprompt.component.ts b/libs/vault/src/components/password-reprompt.component.ts index 11decbd4e65..7665b22be49 100644 --- a/libs/vault/src/components/password-reprompt.component.ts +++ b/libs/vault/src/components/password-reprompt.component.ts @@ -23,7 +23,6 @@ import { KeyService } from "@bitwarden/key-management"; * See UserVerificationComponent for any other situation where you need to verify the user's identity. */ @Component({ - standalone: true, selector: "vault-password-reprompt", imports: [ JslibModule, diff --git a/libs/vault/src/components/totp-countdown/totp-countdown.component.ts b/libs/vault/src/components/totp-countdown/totp-countdown.component.ts index 08587cbb9fa..c634b1165d9 100644 --- a/libs/vault/src/components/totp-countdown/totp-countdown.component.ts +++ b/libs/vault/src/components/totp-countdown/totp-countdown.component.ts @@ -12,7 +12,6 @@ import { TypographyModule } from "@bitwarden/components"; @Component({ selector: "[bitTotpCountdown]", templateUrl: "totp-countdown.component.html", - standalone: true, imports: [CommonModule, TypographyModule], }) export class BitTotpCountdownComponent implements OnInit { From 26caeb3083fcfdcf0b598a99d58285b1dc9e4bf9 Mon Sep 17 00:00:00 2001 From: Addison Beck <github@addisonbeck.com> Date: Mon, 2 Jun 2025 16:38:17 -0400 Subject: [PATCH 220/245] Implement and extend tsconfig.base across projects (#14554) * Implement and extend tsconfig.base across projects * fixup! Merge remote-tracking branch 'origin/main' into rename-tsconfig * fix: import tsconfig.base from components * fix: skip typechecking node modules * fixing tests * fix the tests for real * undo accidentally change --- .gitignore | 3 + apps/browser/jest.config.js | 6 +- apps/browser/tsconfig.json | 52 +--------------- apps/cli/jest.config.js | 14 +++-- apps/cli/tsconfig.json | 36 +---------- apps/desktop/jest.config.js | 6 +- .../tsconfig.json | 9 +-- .../services/desktop-autofill.service.ts | 2 +- apps/desktop/tsconfig.json | 47 +-------------- apps/web/jest.config.js | 6 +- apps/web/tsconfig.json | 37 +----------- bitwarden_license/bit-cli/jest.config.js | 4 +- bitwarden_license/bit-cli/tsconfig.json | 39 +----------- bitwarden_license/bit-common/jest.config.js | 10 ++-- bitwarden_license/bit-common/tsconfig.json | 33 +---------- bitwarden_license/bit-web/jest.config.js | 10 ++-- bitwarden_license/bit-web/tsconfig.json | 36 ----------- jest.config.js | 2 +- libs/admin-console/jest.config.js | 6 +- libs/admin-console/tsconfig.json | 10 +--- libs/angular/jest.config.js | 6 +- libs/angular/tsconfig.json | 24 +------- libs/auth/jest.config.js | 6 +- libs/auth/tsconfig.json | 20 +------ libs/billing/jest.config.js | 4 +- libs/billing/tsconfig.json | 3 +- libs/common/jest.config.js | 4 +- libs/common/tsconfig.json | 12 +--- libs/components/jest.config.js | 6 +- libs/components/tsconfig.json | 13 +--- libs/dirt/card/jest.config.js | 4 +- libs/dirt/card/tsconfig.json | 14 +---- libs/eslint/tsconfig.json | 3 +- libs/importer/jest.config.js | 4 +- libs/importer/tsconfig.json | 19 +----- libs/key-management-ui/jest.config.js | 6 +- libs/key-management-ui/tsconfig.json | 19 +----- libs/key-management/jest.config.js | 6 +- libs/key-management/tsconfig.json | 10 +--- libs/node/jest.config.js | 4 +- libs/node/tsconfig.json | 10 +--- libs/platform/jest.config.js | 4 +- libs/platform/tsconfig.json | 7 +-- libs/shared/tsconfig.json | 20 ------- libs/shared/tsconfig.spec.json | 32 +--------- .../vault-export-core/jest.config.js | 4 +- .../vault-export-core/tsconfig.json | 10 +--- .../vault-export-ui/jest.config.js | 4 +- .../vault-export-ui/tsconfig.json | 23 +------- .../tools/generator/components/jest.config.js | 4 +- libs/tools/generator/components/tsconfig.json | 17 +----- libs/tools/generator/core/jest.config.js | 4 +- libs/tools/generator/core/tsconfig.json | 10 +--- .../extensions/history/jest.config.js | 4 +- .../extensions/history/tsconfig.json | 11 +--- .../extensions/legacy/jest.config.js | 4 +- .../generator/extensions/legacy/tsconfig.json | 13 +--- .../extensions/navigation/jest.config.js | 4 +- .../extensions/navigation/tsconfig.json | 11 +--- libs/tools/send/send-ui/jest.config.js | 4 +- libs/tools/send/send-ui/tsconfig.json | 19 +----- libs/ui/common/tsconfig.json | 7 +-- libs/vault/jest.config.js | 6 +- libs/vault/tsconfig.json | 23 +------- scripts/tsconfig.json | 3 +- tsconfig.base.json | 59 +++++++++++++++++++ tsconfig.json | 54 +---------------- 67 files changed, 179 insertions(+), 747 deletions(-) delete mode 100644 libs/shared/tsconfig.json create mode 100644 tsconfig.base.json diff --git a/.gitignore b/.gitignore index e865fa6a8fb..0fa968aa47c 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,9 @@ build # Testing coverage junit.xml +## The "base" root level folder is expected for some local tests that do +## comparisons between the current branch and a base branch (usually main) +base/ # Misc *.crx diff --git a/apps/browser/jest.config.js b/apps/browser/jest.config.js index 0d1034f0eac..14cd959810e 100644 --- a/apps/browser/jest.config.js +++ b/apps/browser/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); @@ -9,9 +9,9 @@ module.exports = { ...sharedConfig, setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( - { "@bitwarden/common/spec": ["../../libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, + { "@bitwarden/common/spec": ["libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }, ), }; diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index ff4ac340219..a554120bd1e 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -1,55 +1,5 @@ { - "compilerOptions": { - "moduleResolution": "node", - "noImplicitAny": true, - "allowSyntheticDefaultImports": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "module": "ES2020", - "target": "ES2016", - "allowJs": true, - "sourceMap": true, - "baseUrl": ".", - "lib": ["ES2021.String"], - "paths": { - "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], - "@bitwarden/angular/*": ["../../libs/angular/src/*"], - "@bitwarden/auth/common": ["../../libs/auth/src/common"], - "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], - "@bitwarden/billing": ["../../libs/billing/src"], - "@bitwarden/common/*": ["../../libs/common/src/*"], - "@bitwarden/components": ["../../libs/components/src"], - "@bitwarden/dirt-card": ["../../libs/dirt/card/src"], - "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], - "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], - "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], - "@bitwarden/importer-core": ["../../libs/importer/src"], - "@bitwarden/importer-ui": ["../../libs/importer/src/components"], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/key-management-ui": ["../../libs/key-management-ui/src"], - "@bitwarden/platform": ["../../libs/platform/src"], - "@bitwarden/platform/*": ["../../libs/platform/src/*"], - "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/ui-common": ["../../libs/ui/common/src"], - "@bitwarden/ui-common/setup-jest": ["../../libs/ui/common/src/setup-jest"], - "@bitwarden/vault-export-core": [ - "../../libs/tools/export/vault-export/vault-export-core/src" - ], - "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], - "@bitwarden/vault": ["../../libs/vault/src"] - }, - "plugins": [ - { - "name": "typescript-strict-plugin" - } - ], - "useDefineForClassFields": false - }, - "angularCompilerOptions": { - "strictTemplates": true - }, + "extends": "../../tsconfig.base", "include": [ "src", "../../libs/common/src/autofill/constants", diff --git a/apps/cli/jest.config.js b/apps/cli/jest.config.js index e0a5b9ec9cc..c96395944b5 100644 --- a/apps/cli/jest.config.js +++ b/apps/cli/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.ts"); @@ -13,8 +13,14 @@ module.exports = { moduleNameMapper: { "@bitwarden/common/platform/services/sdk/default-sdk-client-factory": "<rootDir>/../../libs/common/spec/jest-sdk-client-factory", - ...pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/", - }), + ...pathsToModuleNameMapper( + { + "@bitwarden/common/spec": ["libs/common/spec"], + ...(compilerOptions?.paths || {}), + }, + { + prefix: "<rootDir>/../../", + }, + ), }, }; diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json index 9d6e3066b29..3bcd098ca19 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -1,38 +1,4 @@ { - "compilerOptions": { - "pretty": true, - "moduleResolution": "node", - "target": "ES2016", - "module": "ES2020", - "noImplicitAny": true, - "allowSyntheticDefaultImports": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "allowJs": true, - "sourceMap": true, - "baseUrl": ".", - "paths": { - "@bitwarden/common/spec": ["../../libs/common/spec"], - "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], - "@bitwarden/auth/common": ["../../libs/auth/src/common"], - "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], - "@bitwarden/common/*": ["../../libs/common/src/*"], - "@bitwarden/importer-core": ["../../libs/importer/src"], - "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], - "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], - "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], - "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], - "@bitwarden/vault-export-core": [ - "../../libs/tools/export/vault-export/vault-export-core/src" - ], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/node/*": ["../../libs/node/src/*"] - }, - "plugins": [ - { - "name": "typescript-strict-plugin" - } - ] - }, + "extends": "../../tsconfig.base", "include": ["src", "src/**/*.spec.ts"] } diff --git a/apps/desktop/jest.config.js b/apps/desktop/jest.config.js index 0d1034f0eac..14cd959810e 100644 --- a/apps/desktop/jest.config.js +++ b/apps/desktop/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); @@ -9,9 +9,9 @@ module.exports = { ...sharedConfig, setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( - { "@bitwarden/common/spec": ["../../libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, + { "@bitwarden/common/spec": ["libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }, ), }; diff --git a/apps/desktop/native-messaging-test-runner/tsconfig.json b/apps/desktop/native-messaging-test-runner/tsconfig.json index 59c7040e509..608e5a3bf4c 100644 --- a/apps/desktop/native-messaging-test-runner/tsconfig.json +++ b/apps/desktop/native-messaging-test-runner/tsconfig.json @@ -1,6 +1,6 @@ { + "extends": "../tsconfig", "compilerOptions": { - "baseUrl": "./", "outDir": "dist", "target": "es6", "module": "CommonJS", @@ -10,12 +10,7 @@ "sourceMap": false, "declaration": false, "paths": { - "@src/*": ["src/*"], - "@bitwarden/admin-console/*": ["../../../libs/admin-console/src/*"], - "@bitwarden/auth/*": ["../../../libs/auth/src/*"], - "@bitwarden/common/*": ["../../../libs/common/src/*"], - "@bitwarden/key-management": ["../../../libs/key-management/src/"], - "@bitwarden/node/*": ["../../../libs/node/src/*"] + "@src/*": ["src/*"] }, "plugins": [ { diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts index d6dddf3b23f..7e60c6b8d76 100644 --- a/apps/desktop/src/autofill/services/desktop-autofill.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts @@ -1,5 +1,4 @@ import { Injectable, OnDestroy } from "@angular/core"; -import { autofill } from "desktop_native/napi"; import { Subject, distinctUntilChanged, @@ -33,6 +32,7 @@ import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { autofill } from "@bitwarden/desktop-napi"; import { NativeAutofillStatusCommand } from "../../platform/main/autofill/status.command"; import { diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index 27e38757e97..7db3e84e451 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -1,50 +1,5 @@ { - "compilerOptions": { - "moduleResolution": "node", - "noImplicitAny": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "module": "ES2020", - "target": "ES2016", - "sourceMap": true, - "types": [], - "baseUrl": ".", - "paths": { - "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], - "@bitwarden/angular/*": ["../../libs/angular/src/*"], - "@bitwarden/auth/common": ["../../libs/auth/src/common"], - "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], - "@bitwarden/billing": ["../../libs/billing/src"], - "@bitwarden/common/*": ["../../libs/common/src/*"], - "@bitwarden/components": ["../../libs/components/src"], - "@bitwarden/dirt-card": ["../../libs/dirt/card/src"], - "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], - "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], - "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], - "@bitwarden/importer-core": ["../../libs/importer/src"], - "@bitwarden/importer-ui": ["../../libs/importer/src/components"], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/key-management-ui": ["../../libs/key-management-ui/src"], - "@bitwarden/node/*": ["../../libs/node/src/*"], - "@bitwarden/platform": ["../../libs/platform/src"], - "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/ui-common": ["../../libs/ui/common/src"], - "@bitwarden/ui-common/setup-jest": ["../../libs/ui/common/src/setup-jest"], - "@bitwarden/vault-export-core": [ - "../../libs/tools/export/vault-export/vault-export-core/src" - ], - "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], - "@bitwarden/vault": ["../../libs/vault/src"] - }, - "plugins": [ - { - "name": "typescript-strict-plugin" - } - ], - "useDefineForClassFields": false - }, + "extends": "../../tsconfig.base", "angularCompilerOptions": { "strictTemplates": true }, diff --git a/apps/web/jest.config.js b/apps/web/jest.config.js index 724e44be009..e1d64fa64c3 100644 --- a/apps/web/jest.config.js +++ b/apps/web/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); @@ -15,11 +15,11 @@ module.exports = { ...pathsToModuleNameMapper( { // lets us use @bitwarden/common/spec in web tests - "@bitwarden/common/spec": ["../../libs/common/spec"], + "@bitwarden/common/spec": ["libs/common/spec"], ...(compilerOptions?.paths ?? {}), }, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }, ), }, diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 0cc988ea722..92cec0d6a2c 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -1,40 +1,5 @@ { - "extends": "../../libs/shared/tsconfig", - "compilerOptions": { - "baseUrl": ".", - "module": "ES2020", - "resolveJsonModule": true, - "paths": { - "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], - "@bitwarden/angular/*": ["../../libs/angular/src/*"], - "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], - "@bitwarden/auth/common": ["../../libs/auth/src/common"], - "@bitwarden/billing": ["../../libs/billing/src"], - "@bitwarden/bit-common/*": ["../../bitwarden_license/bit-common/src/*"], - "@bitwarden/common/*": ["../../libs/common/src/*"], - "@bitwarden/components": ["../../libs/components/src"], - "@bitwarden/dirt-card": ["../../libs/dirt/card/src"], - "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], - "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], - "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], - "@bitwarden/importer-core": ["../../libs/importer/src"], - "@bitwarden/importer-ui": ["../../libs/importer/src/components"], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/key-management-ui": ["../../libs/key-management-ui/src"], - "@bitwarden/platform": ["../../libs/platform/src"], - "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/ui-common": ["../../libs/ui/common/src"], - "@bitwarden/ui-common/setup-jest": ["../../libs/ui/common/src/setup-jest"], - "@bitwarden/vault-export-core": [ - "../../libs/tools/export/vault-export/vault-export-core/src" - ], - "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], - "@bitwarden/vault": ["../../libs/vault/src"], - "@bitwarden/web-vault/*": ["src/*"] - } - }, + "extends": "../../tsconfig.base", "angularCompilerOptions": { "strictTemplates": true }, diff --git a/bitwarden_license/bit-cli/jest.config.js b/bitwarden_license/bit-cli/jest.config.js index 30c9784c326..6a91ba706ed 100644 --- a/bitwarden_license/bit-cli/jest.config.js +++ b/bitwarden_license/bit-cli/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.ts"); @@ -14,7 +14,7 @@ module.exports = { "@bitwarden/common/platform/services/sdk/default-sdk-client-factory": "<rootDir>/../../libs/common/spec/jest-sdk-client-factory", ...pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }), }, }; diff --git a/bitwarden_license/bit-cli/tsconfig.json b/bitwarden_license/bit-cli/tsconfig.json index 4a972b540a7..6630021232f 100644 --- a/bitwarden_license/bit-cli/tsconfig.json +++ b/bitwarden_license/bit-cli/tsconfig.json @@ -1,40 +1,3 @@ { - "compilerOptions": { - "pretty": true, - "moduleResolution": "node", - "target": "ES2016", - "module": "ES2020", - "noImplicitAny": true, - "allowSyntheticDefaultImports": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "allowJs": true, - "sourceMap": true, - "baseUrl": ".", - "paths": { - "@bitwarden/cli/*": ["../../apps/cli/src/*"], - "@bitwarden/common/spec": ["../../libs/common/spec"], - "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], - "@bitwarden/auth/common": ["../../libs/auth/src/common"], - "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], - "@bitwarden/common/*": ["../../libs/common/src/*"], - "@bitwarden/importer-core": ["../../libs/importer/src"], - "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], - "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], - "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], - "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/vault-export-core": [ - "../../libs/tools/export/vault-export/vault-export-core/src" - ], - "@bitwarden/node/*": ["../../libs/node/src/*"], - "@bitwarden/bit-common/*": ["../../bitwarden_license/bit-common/src/*"] - }, - "plugins": [ - { - "name": "typescript-strict-plugin" - } - ] - }, - "include": ["src", "src/**/*.spec.ts"] + "extends": "../../apps/cli/tsconfig" } diff --git a/bitwarden_license/bit-common/jest.config.js b/bitwarden_license/bit-common/jest.config.js index ab31a4c26ca..31f15253ebd 100644 --- a/bitwarden_license/bit-common/jest.config.js +++ b/bitwarden_license/bit-common/jest.config.js @@ -1,5 +1,5 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); /** @type {import('jest').Config} */ @@ -9,13 +9,13 @@ module.exports = { testEnvironment: "jsdom", moduleNameMapper: pathsToModuleNameMapper( { - "@bitwarden/common/spec": ["../../libs/common/spec"], - "@bitwarden/common": ["../../libs/common/src/*"], - "@bitwarden/admin-console/common": ["<rootDir>/libs/admin-console/src/common"], + "@bitwarden/common/spec": ["libs/common/spec"], + "@bitwarden/common": ["libs/common/src/*"], + "@bitwarden/admin-console/common": ["libs/admin-console/src/common"], ...(compilerOptions?.paths ?? {}), }, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }, ), setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], diff --git a/bitwarden_license/bit-common/tsconfig.json b/bitwarden_license/bit-common/tsconfig.json index c92167bb919..9c607a26b09 100644 --- a/bitwarden_license/bit-common/tsconfig.json +++ b/bitwarden_license/bit-common/tsconfig.json @@ -1,34 +1,5 @@ { - "extends": "../../libs/shared/tsconfig", + "extends": "../../tsconfig.base", "include": ["src", "spec"], - "exclude": ["node_modules", "dist"], - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], - "@bitwarden/angular/*": ["../../libs/angular/src/*"], - "@bitwarden/auth": ["../../libs/auth/src"], - "@bitwarden/billing": ["../../libs/billing/src"], - "@bitwarden/bit-common/*": ["../bit-common/src/*"], - "@bitwarden/common/*": ["../../libs/common/src/*"], - "@bitwarden/components": ["../../libs/components/src"], - "@bitwarden/dirt-card": ["../../libs/dirt/card/src"], - "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], - "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], - "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/platform": ["../../libs/platform/src"], - "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/ui-common": ["../../libs/ui/common/src"], - "@bitwarden/ui-common/setup-jest": ["../../libs/ui/common/src/setup-jest"], - "@bitwarden/vault-export-core": [ - "../../libs/tools/export/vault-export/vault-export-core/src" - ], - "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-core/src"], - "@bitwarden/vault": ["../../libs/vault/src"], - "@bitwarden/web-vault/*": ["../../apps/web/src/*"] - } - } + "exclude": ["node_modules", "dist"] } diff --git a/bitwarden_license/bit-web/jest.config.js b/bitwarden_license/bit-web/jest.config.js index 9eefd99528a..5934882e731 100644 --- a/bitwarden_license/bit-web/jest.config.js +++ b/bitwarden_license/bit-web/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); @@ -10,13 +10,13 @@ module.exports = { setupFilesAfterEnv: ["../../apps/web/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( { - "@bitwarden/common/spec": ["../../libs/common/spec"], - "@bitwarden/common": ["../../libs/common/src/*"], - "@bitwarden/admin-console/common": ["<rootDir>/libs/admin-console/src/common"], + "@bitwarden/common/spec": ["libs/common/spec"], + "@bitwarden/common": ["libs/common/src/*"], + "@bitwarden/admin-console/common": ["libs/admin-console/src/common"], ...(compilerOptions?.paths ?? {}), }, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }, ), }; diff --git a/bitwarden_license/bit-web/tsconfig.json b/bitwarden_license/bit-web/tsconfig.json index 66b83755aa8..7ec0441f4c1 100644 --- a/bitwarden_license/bit-web/tsconfig.json +++ b/bitwarden_license/bit-web/tsconfig.json @@ -1,41 +1,5 @@ { "extends": "../../apps/web/tsconfig", - "compilerOptions": { - "baseUrl": ".", - "module": "ES2020", - "resolveJsonModule": true, - "paths": { - "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], - "@bitwarden/angular/*": ["../../libs/angular/src/*"], - "@bitwarden/auth/common": ["../../libs/auth/src/common"], - "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], - "@bitwarden/billing": ["../../libs/billing/src"], - "@bitwarden/common/*": ["../../libs/common/src/*"], - "@bitwarden/components": ["../../libs/components/src"], - "@bitwarden/dirt-card": ["../../libs/dirt/card/src"], - "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], - "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], - "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], - "@bitwarden/vault-export-core": [ - "../../libs/tools/export/vault-export/vault-export-core/src" - ], - "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], - "@bitwarden/importer-core": ["../../libs/importer/src"], - "@bitwarden/importer-ui": ["../../libs/importer/src/components"], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/key-management-ui": ["../../libs/key-management-ui/src"], - "@bitwarden/platform": ["../../libs/platform/src"], - "@bitwarden/ui-common": ["../../libs/ui/common/src"], - "@bitwarden/ui-common/setup-jest": ["../../libs/ui/common/src/setup-jest"], - "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/vault": ["../../libs/vault/src"], - "@bitwarden/web-vault/*": ["../../apps/web/src/*"], - - "@bitwarden/bit-common/*": ["../bit-common/src/*"] - } - }, "files": [ "../../apps/web/src/polyfills.ts", "../../apps/web/src/main.ts", diff --git a/jest.config.js b/jest.config.js index e8815f92ffb..b0ffd2382ca 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("./tsconfig.base"); /** @type {import('jest').Config} */ module.exports = { diff --git a/libs/admin-console/jest.config.js b/libs/admin-console/jest.config.js index d59da5d68f5..48f498a2d7f 100644 --- a/libs/admin-console/jest.config.js +++ b/libs/admin-console/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.spec"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); @@ -11,9 +11,9 @@ module.exports = { setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( // lets us use @bitwarden/common/spec in tests - { "@bitwarden/common/spec": ["../common/spec"], ...(compilerOptions?.paths ?? {}) }, + { "@bitwarden/common/spec": ["libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }, ), }; diff --git a/libs/admin-console/tsconfig.json b/libs/admin-console/tsconfig.json index 4f057fd6af0..72e2a434344 100644 --- a/libs/admin-console/tsconfig.json +++ b/libs/admin-console/tsconfig.json @@ -1,13 +1,5 @@ { - "extends": "../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../admin-console/src/common"], - "@bitwarden/auth/common": ["../auth/src/common"], - "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/key-management": ["../key-management/src"] - } - }, + "extends": "../../tsconfig.base", "include": ["src", "spec", "../../libs/common/custom-matchers.d.ts"], "exclude": ["node_modules", "dist"] } diff --git a/libs/angular/jest.config.js b/libs/angular/jest.config.js index 66a7e9a687a..e94ae5e9af4 100644 --- a/libs/angular/jest.config.js +++ b/libs/angular/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.spec"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); @@ -11,9 +11,9 @@ module.exports = { setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( // lets us use @bitwarden/common/spec in tests - { "@bitwarden/common/spec": ["../common/spec"], ...(compilerOptions?.paths ?? {}) }, + { "@bitwarden/common/spec": ["libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }, ), }; diff --git a/libs/angular/tsconfig.json b/libs/angular/tsconfig.json index d77e56d778e..9c607a26b09 100644 --- a/libs/angular/tsconfig.json +++ b/libs/angular/tsconfig.json @@ -1,27 +1,5 @@ { - "extends": "../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../admin-console/src/common"], - "@bitwarden/angular/*": ["../angular/src/*"], - "@bitwarden/auth/angular": ["../auth/src/angular"], - "@bitwarden/auth/common": ["../auth/src/common"], - "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/components": ["../components/src"], - "@bitwarden/generator-components": ["../tools/generator/components/src"], - "@bitwarden/generator-core": ["../tools/generator/core/src"], - "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], - "@bitwarden/importer/core": ["../importer/src"], - "@bitwarden/importer-ui": ["../importer/src/components"], - "@bitwarden/key-management": ["../key-management/src"], - "@bitwarden/platform": ["../platform/src"], - "@bitwarden/ui-common": ["../ui/common/src"], - "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"], - "@bitwarden/vault": ["../vault/src"] - } - }, + "extends": "../../tsconfig.base", "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/auth/jest.config.js b/libs/auth/jest.config.js index 79b054f0741..815ac5c30c2 100644 --- a/libs/auth/jest.config.js +++ b/libs/auth/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.spec"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); @@ -11,9 +11,9 @@ module.exports = { setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( // lets us use @bitwarden/common/spec in tests - { "@bitwarden/common/spec": ["../common/spec"], ...(compilerOptions?.paths ?? {}) }, + { "@bitwarden/common/spec": ["libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }, ), }; diff --git a/libs/auth/tsconfig.json b/libs/auth/tsconfig.json index 8d08522ffce..9c607a26b09 100644 --- a/libs/auth/tsconfig.json +++ b/libs/auth/tsconfig.json @@ -1,23 +1,5 @@ { - "extends": "../shared/tsconfig", - "compilerOptions": { - "resolveJsonModule": true, - "paths": { - "@bitwarden/admin-console/common": ["../admin-console/src/common"], - "@bitwarden/angular/*": ["../angular/src/*"], - "@bitwarden/auth/angular": ["../auth/src/angular"], - "@bitwarden/auth/common": ["../auth/src/common"], - "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/components": ["../components/src"], - "@bitwarden/generator-core": ["../tools/generator/core/src"], - "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], - "@bitwarden/key-management": ["../key-management/src"], - "@bitwarden/platform": ["../platform/src"], - "@bitwarden/ui-common": ["../ui/common/src"] - } - }, + "extends": "../../tsconfig.base", "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/billing/jest.config.js b/libs/billing/jest.config.js index 5c24975e836..0aa13381668 100644 --- a/libs/billing/jest.config.js +++ b/libs/billing/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.spec"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); @@ -10,6 +10,6 @@ module.exports = { displayName: "libs/billing tests", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }), }; diff --git a/libs/billing/tsconfig.json b/libs/billing/tsconfig.json index bb08eb89d1c..9c607a26b09 100644 --- a/libs/billing/tsconfig.json +++ b/libs/billing/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../shared/tsconfig", - "compilerOptions": {}, + "extends": "../../tsconfig.base", "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/common/jest.config.js b/libs/common/jest.config.js index 7e6c0997b9c..a1e14ee62f8 100644 --- a/libs/common/jest.config.js +++ b/libs/common/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.spec"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../shared/jest.config.ts"); @@ -12,6 +12,6 @@ module.exports = { testEnvironment: "jsdom", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }), }; diff --git a/libs/common/tsconfig.json b/libs/common/tsconfig.json index 03f66196a30..1d81cc8c221 100644 --- a/libs/common/tsconfig.json +++ b/libs/common/tsconfig.json @@ -1,15 +1,5 @@ { - "extends": "../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../admin-console/src/common"], - "@bitwarden/auth/common": ["../auth/src/common"], - // TODO: Remove once circular dependencies in admin-console, auth and key-management are resolved - "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/key-management": ["../key-management/src"], - "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"] - } - }, + "extends": "../../tsconfig.base", "include": ["src", "spec", "./custom-matchers.d.ts", "../key-management/src/index.ts"], "exclude": ["node_modules", "dist"] } diff --git a/libs/components/jest.config.js b/libs/components/jest.config.js index 082d378dced..4310480dd29 100644 --- a/libs/components/jest.config.js +++ b/libs/components/jest.config.js @@ -1,8 +1,8 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("../../tsconfig.base"); -const sharedConfig = require("../../libs/shared/jest.config.angular"); +const sharedConfig = require("../shared/jest.config.angular"); /** @type {import('jest').Config} */ module.exports = { @@ -10,6 +10,6 @@ module.exports = { displayName: "libs/components tests", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }), }; diff --git a/libs/components/tsconfig.json b/libs/components/tsconfig.json index 8f2b5edcc90..3706663ecf1 100644 --- a/libs/components/tsconfig.json +++ b/libs/components/tsconfig.json @@ -1,14 +1,3 @@ { - "extends": "../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/platform": ["../platform/src"], - "@bitwarden/ui-common": ["../ui/common/src"], - "@bitwarden/auth/common": ["../auth/src/common"], - "@bitwarden/admin-console/common": ["../admin-console/src/common"], - "@bitwarden/key-management": ["../key-management/src"], - "@bitwarden/ui-common/setup-jest": ["../ui/common/src/setup-jest"] - } - } + "extends": "../../tsconfig.base" } diff --git a/libs/dirt/card/jest.config.js b/libs/dirt/card/jest.config.js index 68455588d66..03ffc631f76 100644 --- a/libs/dirt/card/jest.config.js +++ b/libs/dirt/card/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../shared/tsconfig.spec"); +const { compilerOptions } = require("../../../../tsconfig.base"); const sharedConfig = require("../../shared/jest.config.angular"); @@ -10,6 +10,6 @@ module.exports = { displayName: "tools/card tests", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/../../", + prefix: "<rootDir>/../../../", }), }; diff --git a/libs/dirt/card/tsconfig.json b/libs/dirt/card/tsconfig.json index 050a1748b7b..941c32b5b2a 100644 --- a/libs/dirt/card/tsconfig.json +++ b/libs/dirt/card/tsconfig.json @@ -1,17 +1,5 @@ { - "extends": "../../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../../admin-console/src/common"], - "@bitwarden/angular/*": ["../../angular/src/*"], - "@bitwarden/auth/common": ["../../auth/src/common"], - "@bitwarden/common/*": ["../../common/src/*"], - "@bitwarden/components": ["../../components/src"], - "@bitwarden/key-management": ["../../key-management/src"], - "@bitwarden/platform": ["../../platform/src"], - "@bitwarden/ui-common": ["../../ui/common/src"] - } - }, + "extends": "../../../tsconfig.base", "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/eslint/tsconfig.json b/libs/eslint/tsconfig.json index bbf065886c4..69be0fa9cac 100644 --- a/libs/eslint/tsconfig.json +++ b/libs/eslint/tsconfig.json @@ -1,6 +1,5 @@ { - "extends": "../shared/tsconfig", - "compilerOptions": {}, + "extends": "../../tsconfig.base", "exclude": ["node_modules", "dist"], "files": ["empty.ts"] } diff --git a/libs/importer/jest.config.js b/libs/importer/jest.config.js index 8d782d913a8..0d7db28409f 100644 --- a/libs/importer/jest.config.js +++ b/libs/importer/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.spec"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../shared/jest.config.ts"); @@ -9,6 +9,6 @@ module.exports = { ...sharedConfig, testEnvironment: "jsdom", moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }), }; diff --git a/libs/importer/tsconfig.json b/libs/importer/tsconfig.json index e16a16a0337..919c7bf77bf 100644 --- a/libs/importer/tsconfig.json +++ b/libs/importer/tsconfig.json @@ -1,22 +1,5 @@ { - "extends": "../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../admin-console/src/common"], - "@bitwarden/angular/*": ["../angular/src/*"], - "@bitwarden/auth/common": ["../auth/src/common"], - "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/components": ["../components/src"], - "@bitwarden/generator-core": ["../tools/generator/core/src"], - "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], - "@bitwarden/key-management": ["../key-management/src"], - "@bitwarden/platform": ["../platform/src"], - "@bitwarden/ui-common": ["../ui/common/src"], - "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"] - } - }, + "extends": "../../tsconfig.base", "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/key-management-ui/jest.config.js b/libs/key-management-ui/jest.config.js index ceeee3b2445..6a1fbdd63e1 100644 --- a/libs/key-management-ui/jest.config.js +++ b/libs/key-management-ui/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.spec"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); @@ -11,9 +11,9 @@ module.exports = { setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( // lets us use @bitwarden/common/spec in tests - { "@bitwarden/common/spec": ["../common/spec"], ...(compilerOptions?.paths ?? {}) }, + { "@bitwarden/common/spec": ["libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }, ), }; diff --git a/libs/key-management-ui/tsconfig.json b/libs/key-management-ui/tsconfig.json index bb263f3a2b9..9c607a26b09 100644 --- a/libs/key-management-ui/tsconfig.json +++ b/libs/key-management-ui/tsconfig.json @@ -1,22 +1,5 @@ { - "extends": "../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../admin-console/src/common"], - "@bitwarden/angular/*": ["../angular/src/*"], - "@bitwarden/auth/angular": ["../auth/src/angular"], - "@bitwarden/auth/common": ["../auth/src/common"], - "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/components": ["../components/src/index.ts"], - "@bitwarden/generator-core": ["../tools/generator/core/src"], - "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], - "@bitwarden/key-management": ["../key-management/src/index.ts"], - "@bitwarden/platform": ["../platform/src"], - "@bitwarden/ui-common": ["../ui/common/src"] - } - }, + "extends": "../../tsconfig.base", "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/key-management/jest.config.js b/libs/key-management/jest.config.js index 4da81b0bd13..a074621a098 100644 --- a/libs/key-management/jest.config.js +++ b/libs/key-management/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.spec"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); @@ -11,9 +11,9 @@ module.exports = { setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( // lets us use @bitwarden/common/spec in tests - { "@bitwarden/common/spec": ["../common/spec"], ...(compilerOptions?.paths ?? {}) }, + { "@bitwarden/common/spec": ["libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }, ), }; diff --git a/libs/key-management/tsconfig.json b/libs/key-management/tsconfig.json index 3d22cb2ec51..9c607a26b09 100644 --- a/libs/key-management/tsconfig.json +++ b/libs/key-management/tsconfig.json @@ -1,13 +1,5 @@ { - "extends": "../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../admin-console/src/common"], - "@bitwarden/auth/common": ["../auth/src/common"], - "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/key-management": ["../key-management/src"] - } - }, + "extends": "../../tsconfig.base", "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/node/jest.config.js b/libs/node/jest.config.js index 1a33ad39406..d765efcfa2c 100644 --- a/libs/node/jest.config.js +++ b/libs/node/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.spec"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../shared/jest.config.ts"); @@ -11,6 +11,6 @@ module.exports = { testEnvironment: "node", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }), }; diff --git a/libs/node/tsconfig.json b/libs/node/tsconfig.json index 3d22cb2ec51..9c607a26b09 100644 --- a/libs/node/tsconfig.json +++ b/libs/node/tsconfig.json @@ -1,13 +1,5 @@ { - "extends": "../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../admin-console/src/common"], - "@bitwarden/auth/common": ["../auth/src/common"], - "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/key-management": ["../key-management/src"] - } - }, + "extends": "../../tsconfig.base", "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/platform/jest.config.js b/libs/platform/jest.config.js index 7d190940909..174c430a901 100644 --- a/libs/platform/jest.config.js +++ b/libs/platform/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.spec"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); @@ -10,6 +10,6 @@ module.exports = { displayName: "libs/platform tests", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }), }; diff --git a/libs/platform/tsconfig.json b/libs/platform/tsconfig.json index 898f9e41c6a..9c607a26b09 100644 --- a/libs/platform/tsconfig.json +++ b/libs/platform/tsconfig.json @@ -1,10 +1,5 @@ { - "extends": "../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/common/*": ["../common/src/*"] - } - }, + "extends": "../../tsconfig.base", "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/libs/shared/tsconfig.json b/libs/shared/tsconfig.json deleted file mode 100644 index 2161d2fb7d1..00000000000 --- a/libs/shared/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "pretty": true, - "moduleResolution": "node", - "noImplicitAny": true, - "target": "ES6", - "module": "es2020", - "lib": ["es5", "es6", "es7", "dom"], - "sourceMap": true, - "allowSyntheticDefaultImports": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "outDir": "dist", - "plugins": [ - { - "name": "typescript-strict-plugin" - } - ] - } -} diff --git a/libs/shared/tsconfig.spec.json b/libs/shared/tsconfig.spec.json index 5402594e5ab..3706663ecf1 100644 --- a/libs/shared/tsconfig.spec.json +++ b/libs/shared/tsconfig.spec.json @@ -1,33 +1,3 @@ { - "extends": "./tsconfig", - "compilerOptions": { - "resolveJsonModule": true, - "paths": { - "@bitwarden/admin-console/common": ["../admin-console/src/common"], - "@bitwarden/angular/*": ["../angular/src/*"], - "@bitwarden/auth/angular": ["../auth/src/angular"], - "@bitwarden/auth/common": ["../auth/src/common"], - "@bitwarden/billing": ["../billing/src"], - "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/components": ["../components/src"], - "@bitwarden/dirt-card": ["../dirt/card/src"], - "@bitwarden/generator-components": ["../tools/generator/components/src"], - "@bitwarden/generator-core": ["../tools/generator/core/src"], - "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], - "@bitwarden/importer-core": ["../importer/src"], - "@bitwarden/importer-ui": ["../importer/src/components"], - "@bitwarden/key-management": ["../key-management/src"], - "@bitwarden/key-management-ui": ["../key-management-ui/src/index.ts"], - "@bitwarden/node/*": ["../node/src/*"], - "@bitwarden/platform": ["../platform/src"], - "@bitwarden/send-ui": ["../tools/send/send-ui/src"], - "@bitwarden/ui-common": ["../ui/common/src"], - "@bitwarden/ui-common/setup-jest": ["../ui/common/src/setup-jest"], - "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"], - "@bitwarden/vault-export-ui": ["../tools/export/vault-export/vault-export-ui/src"], - "@bitwarden/vault": ["../vault/src"] - } - } + "extends": "../../tsconfig.base" } diff --git a/libs/tools/export/vault-export/vault-export-core/jest.config.js b/libs/tools/export/vault-export/vault-export-core/jest.config.js index 0a78a9855dc..066309a8bfc 100644 --- a/libs/tools/export/vault-export/vault-export-core/jest.config.js +++ b/libs/tools/export/vault-export/vault-export-core/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../../shared/tsconfig.spec"); +const { compilerOptions } = require("../../../../../tsconfig.base"); /** @type {import('jest').Config} */ module.exports = { @@ -8,6 +8,6 @@ module.exports = { preset: "ts-jest", testEnvironment: "jsdom", moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/../../../", + prefix: "<rootDir>/../../../../../", }), }; diff --git a/libs/tools/export/vault-export/vault-export-core/tsconfig.json b/libs/tools/export/vault-export/vault-export-core/tsconfig.json index 7652a271044..06123643d63 100644 --- a/libs/tools/export/vault-export/vault-export-core/tsconfig.json +++ b/libs/tools/export/vault-export/vault-export-core/tsconfig.json @@ -1,13 +1,5 @@ { - "extends": "../../../../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../../../../admin-console/src/common"], - "@bitwarden/auth/common": ["../../../../auth/src/common"], - "@bitwarden/common/*": ["../../../../common/src/*"], - "@bitwarden/key-management": ["../../../../key-management/src"] - } - }, + "extends": "../../../../../tsconfig", "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/export/vault-export/vault-export-ui/jest.config.js b/libs/tools/export/vault-export/vault-export-ui/jest.config.js index 0a78a9855dc..066309a8bfc 100644 --- a/libs/tools/export/vault-export/vault-export-ui/jest.config.js +++ b/libs/tools/export/vault-export/vault-export-ui/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../../shared/tsconfig.spec"); +const { compilerOptions } = require("../../../../../tsconfig.base"); /** @type {import('jest').Config} */ module.exports = { @@ -8,6 +8,6 @@ module.exports = { preset: "ts-jest", testEnvironment: "jsdom", moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/../../../", + prefix: "<rootDir>/../../../../../", }), }; diff --git a/libs/tools/export/vault-export/vault-export-ui/tsconfig.json b/libs/tools/export/vault-export/vault-export-ui/tsconfig.json index 6f2a0242dac..06123643d63 100644 --- a/libs/tools/export/vault-export/vault-export-ui/tsconfig.json +++ b/libs/tools/export/vault-export/vault-export-ui/tsconfig.json @@ -1,26 +1,5 @@ { - "extends": "../../../../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../../../../admin-console/src/common"], - "@bitwarden/angular/*": ["../../../../angular/src/*"], - "@bitwarden/auth/angular": ["../../../../auth/src/angular"], - "@bitwarden/auth/common": ["../../../../auth/src/common"], - "@bitwarden/common/*": ["../../../../common/src/*"], - "@bitwarden/components": ["../../../../components/src"], - "@bitwarden/generator-core": ["../../../../tools/generator/core/src"], - "@bitwarden/generator-components": ["../../../../tools/generator/components/src"], - "@bitwarden/generator-history": ["../../../../tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["../../../../tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../../../../tools/generator/extensions/navigation/src"], - "@bitwarden/key-management": ["../../../../key-management/src"], - "@bitwarden/platform": ["../../../../platform/src"], - "@bitwarden/ui-common": ["../../../../ui/common/src"], - "@bitwarden/vault-export-core": [ - "../../../../tools/export/vault-export/vault-export-core/src" - ] - } - }, + "extends": "../../../../../tsconfig", "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/generator/components/jest.config.js b/libs/tools/generator/components/jest.config.js index bf5e465f398..e8a7d433d1d 100644 --- a/libs/tools/generator/components/jest.config.js +++ b/libs/tools/generator/components/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../shared/tsconfig.spec.json"); +const { compilerOptions } = require("../../../../tsconfig.base"); /** @type {import('jest').Config} */ module.exports = { @@ -8,6 +8,6 @@ module.exports = { preset: "ts-jest", testEnvironment: "../../../shared/test.environment.ts", moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/../../", + prefix: "<rootDir>/../../../../", }), }; diff --git a/libs/tools/generator/components/tsconfig.json b/libs/tools/generator/components/tsconfig.json index 9a3a08b40fc..5010a206c9b 100644 --- a/libs/tools/generator/components/tsconfig.json +++ b/libs/tools/generator/components/tsconfig.json @@ -1,20 +1,5 @@ { - "extends": "../../../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../../../admin-console/src/common"], - "@bitwarden/angular/*": ["../../../angular/src/*"], - "@bitwarden/auth/common": ["../../../auth/src/common"], - "@bitwarden/common/*": ["../../../common/src/*"], - "@bitwarden/components": ["../../../components/src"], - "@bitwarden/generator-core": ["../../../tools/generator/core/src"], - "@bitwarden/generator-history": ["../../../tools/generator/extensions/history/src"], - "@bitwarden/key-management": ["../../../key-management/src"], - "@bitwarden/platform": ["../../../platform/src"], - "@bitwarden/ui-common": ["../../../ui/common/src"], - "@bitwarden/vault": ["../../../vault/src"] - } - }, + "extends": "../../../../tsconfig.base", "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/generator/core/jest.config.js b/libs/tools/generator/core/jest.config.js index b052672c4af..e8a7d433d1d 100644 --- a/libs/tools/generator/core/jest.config.js +++ b/libs/tools/generator/core/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../shared/tsconfig.spec"); +const { compilerOptions } = require("../../../../tsconfig.base"); /** @type {import('jest').Config} */ module.exports = { @@ -8,6 +8,6 @@ module.exports = { preset: "ts-jest", testEnvironment: "../../../shared/test.environment.ts", moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/../../", + prefix: "<rootDir>/../../../../", }), }; diff --git a/libs/tools/generator/core/tsconfig.json b/libs/tools/generator/core/tsconfig.json index 303f913ba23..5010a206c9b 100644 --- a/libs/tools/generator/core/tsconfig.json +++ b/libs/tools/generator/core/tsconfig.json @@ -1,13 +1,5 @@ { - "extends": "../../../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../../../admin-console/src/common"], - "@bitwarden/auth/common": ["../../../auth/src/common"], - "@bitwarden/common/*": ["../../../common/src/*"], - "@bitwarden/key-management": ["../../../key-management/src"] - } - }, + "extends": "../../../../tsconfig.base", "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/generator/extensions/history/jest.config.js b/libs/tools/generator/extensions/history/jest.config.js index f90801cd7c4..598c83fe7d7 100644 --- a/libs/tools/generator/extensions/history/jest.config.js +++ b/libs/tools/generator/extensions/history/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../../shared/tsconfig.spec"); +const { compilerOptions } = require("../../../../../tsconfig.base"); /** @type {import('jest').Config} */ module.exports = { @@ -8,6 +8,6 @@ module.exports = { preset: "ts-jest", testEnvironment: "../../../../shared/test.environment.ts", moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/../../../", + prefix: "<rootDir>/../../../../../", }), }; diff --git a/libs/tools/generator/extensions/history/tsconfig.json b/libs/tools/generator/extensions/history/tsconfig.json index 5fc1caf014f..84e562664f4 100644 --- a/libs/tools/generator/extensions/history/tsconfig.json +++ b/libs/tools/generator/extensions/history/tsconfig.json @@ -1,14 +1,5 @@ { - "extends": "../../../../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../../../../admin-console/src/common"], - "@bitwarden/auth/common": ["../../../../auth/src/common"], - "@bitwarden/common/*": ["../../../../common/src/*"], - "@bitwarden/generator-core": ["../../../../tools/generator/core/src"], - "@bitwarden/key-management": ["../../../../key-management/src"] - } - }, + "extends": "../../../../../tsconfig.base", "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/generator/extensions/legacy/jest.config.js b/libs/tools/generator/extensions/legacy/jest.config.js index f90801cd7c4..598c83fe7d7 100644 --- a/libs/tools/generator/extensions/legacy/jest.config.js +++ b/libs/tools/generator/extensions/legacy/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../../shared/tsconfig.spec"); +const { compilerOptions } = require("../../../../../tsconfig.base"); /** @type {import('jest').Config} */ module.exports = { @@ -8,6 +8,6 @@ module.exports = { preset: "ts-jest", testEnvironment: "../../../../shared/test.environment.ts", moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/../../../", + prefix: "<rootDir>/../../../../../", }), }; diff --git a/libs/tools/generator/extensions/legacy/tsconfig.json b/libs/tools/generator/extensions/legacy/tsconfig.json index 9a09e28ea3d..06123643d63 100644 --- a/libs/tools/generator/extensions/legacy/tsconfig.json +++ b/libs/tools/generator/extensions/legacy/tsconfig.json @@ -1,16 +1,5 @@ { - "extends": "../../../../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../../../../admin-console/src/common"], - "@bitwarden/auth/common": ["../../../../auth/src/common"], - "@bitwarden/common/*": ["../../../../common/src/*"], - "@bitwarden/generator-core": ["../../../../tools/generator/core/src"], - "@bitwarden/generator-history": ["../../../../tools/generator/extensions/history/src"], - "@bitwarden/generator-navigation": ["../../../../tools/generator/extensions/navigation/src"], - "@bitwarden/key-management": ["../../../../key-management/src"] - } - }, + "extends": "../../../../../tsconfig", "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/generator/extensions/navigation/jest.config.js b/libs/tools/generator/extensions/navigation/jest.config.js index f90801cd7c4..598c83fe7d7 100644 --- a/libs/tools/generator/extensions/navigation/jest.config.js +++ b/libs/tools/generator/extensions/navigation/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../../shared/tsconfig.spec"); +const { compilerOptions } = require("../../../../../tsconfig.base"); /** @type {import('jest').Config} */ module.exports = { @@ -8,6 +8,6 @@ module.exports = { preset: "ts-jest", testEnvironment: "../../../../shared/test.environment.ts", moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/../../../", + prefix: "<rootDir>/../../../../../", }), }; diff --git a/libs/tools/generator/extensions/navigation/tsconfig.json b/libs/tools/generator/extensions/navigation/tsconfig.json index 5fc1caf014f..06123643d63 100644 --- a/libs/tools/generator/extensions/navigation/tsconfig.json +++ b/libs/tools/generator/extensions/navigation/tsconfig.json @@ -1,14 +1,5 @@ { - "extends": "../../../../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../../../../admin-console/src/common"], - "@bitwarden/auth/common": ["../../../../auth/src/common"], - "@bitwarden/common/*": ["../../../../common/src/*"], - "@bitwarden/generator-core": ["../../../../tools/generator/core/src"], - "@bitwarden/key-management": ["../../../../key-management/src"] - } - }, + "extends": "../../../../../tsconfig", "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/tools/send/send-ui/jest.config.js b/libs/tools/send/send-ui/jest.config.js index ed8dbe61163..2ab935f0bfd 100644 --- a/libs/tools/send/send-ui/jest.config.js +++ b/libs/tools/send/send-ui/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../../../shared/tsconfig.spec"); +const { compilerOptions } = require("../../../../tsconfig.base"); const { createCjsPreset } = require("jest-preset-angular/presets"); @@ -21,6 +21,6 @@ module.exports = { displayName: "tools/send-ui tests", setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "<rootDir>/../../", + prefix: "<rootDir>/../../../../", }), }; diff --git a/libs/tools/send/send-ui/tsconfig.json b/libs/tools/send/send-ui/tsconfig.json index e6d6680ad40..5010a206c9b 100644 --- a/libs/tools/send/send-ui/tsconfig.json +++ b/libs/tools/send/send-ui/tsconfig.json @@ -1,22 +1,5 @@ { - "extends": "../../../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/admin-console/common": ["../../../admin-console/src/common"], - "@bitwarden/angular/*": ["../../../angular/src/*"], - "@bitwarden/auth/common": ["../../../auth/src/common"], - "@bitwarden/common/*": ["../../../common/src/*"], - "@bitwarden/components": ["../../../components/src"], - "@bitwarden/generator-components": ["../../../tools/generator/components/src"], - "@bitwarden/generator-core": ["../../../tools/generator/core/src"], - "@bitwarden/generator-history": ["../../../tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["../../../tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../../../tools/generator/extensions/navigation/src"], - "@bitwarden/key-management": ["../../../key-management/src"], - "@bitwarden/platform": ["../../../platform/src"], - "@bitwarden/ui-common": ["../../../ui/common/src"] - } - }, + "extends": "../../../../tsconfig.base", "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/ui/common/tsconfig.json b/libs/ui/common/tsconfig.json index 31062d41a1c..941c32b5b2a 100644 --- a/libs/ui/common/tsconfig.json +++ b/libs/ui/common/tsconfig.json @@ -1,10 +1,5 @@ { - "extends": "../../shared/tsconfig", - "compilerOptions": { - "paths": { - "@bitwarden/common/*": ["../../common/src/*"] - } - }, + "extends": "../../../tsconfig.base", "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/libs/vault/jest.config.js b/libs/vault/jest.config.js index c0a0103da73..05c5b5c5d3a 100644 --- a/libs/vault/jest.config.js +++ b/libs/vault/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("../shared/tsconfig.spec"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); @@ -11,9 +11,9 @@ module.exports = { setupFilesAfterEnv: ["<rootDir>/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( // lets us use @bitwarden/common/spec in tests - { "@bitwarden/common/spec": ["../common/spec"], ...(compilerOptions?.paths ?? {}) }, + { "@bitwarden/common/spec": ["libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, { - prefix: "<rootDir>/", + prefix: "<rootDir>/../../", }, ), }; diff --git a/libs/vault/tsconfig.json b/libs/vault/tsconfig.json index 6039dccd811..9c607a26b09 100644 --- a/libs/vault/tsconfig.json +++ b/libs/vault/tsconfig.json @@ -1,26 +1,5 @@ { - "extends": "../shared/tsconfig", - "compilerOptions": { - "resolveJsonModule": true, - "paths": { - "@bitwarden/admin-console/common": ["../admin-console/src/common"], - "@bitwarden/angular/*": ["../angular/src/*"], - "@bitwarden/auth/common": ["../auth/src/common"], - "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/components": ["../components/src"], - "@bitwarden/importer-ui": ["../importer/src/components"], - "@bitwarden/generator-components": ["../tools/generator/components/src"], - "@bitwarden/generator-core": ["../tools/generator/core/src"], - "@bitwarden/generator-history": ["../tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["../tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../tools/generator/extensions/navigation/src"], - "@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"], - "@bitwarden/key-management": ["../key-management/src"], - "@bitwarden/platform": ["../platform/src"], - "@bitwarden/ui-common": ["../ui/common/src"], - "@bitwarden/vault": ["../vault/src"] - } - }, + "extends": "../../tsconfig.base", "include": ["src", "spec"], "exclude": ["node_modules", "dist"] } diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index 18ce94164c9..c346fdde300 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -1,7 +1,6 @@ { - "extends": "../libs/shared/tsconfig", + "extends": "../tsconfig.base", "compilerOptions": { - "strict": true, "outDir": "dist", "module": "NodeNext", "moduleResolution": "NodeNext", diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 00000000000..7053ec66aa4 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,59 @@ +{ + "compilerOptions": { + "strict": false, + "pretty": true, + "moduleResolution": "node", + "noImplicitAny": true, + "target": "ES2016", + "module": "ES2020", + "lib": ["es5", "es6", "es7", "dom", "ES2021", "ESNext.Disposable"], + "sourceMap": true, + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "declaration": false, + "outDir": "dist", + "baseUrl": ".", + "resolveJsonModule": true, + "allowJs": true, + "sourceMap": true, + "skipLibCheck": true, + "paths": { + "@bitwarden/admin-console/common": ["./libs/admin-console/src/common"], + "@bitwarden/angular/*": ["./libs/angular/src/*"], + "@bitwarden/auth/angular": ["./libs/auth/src/angular"], + "@bitwarden/auth/common": ["./libs/auth/src/common"], + "@bitwarden/billing": ["./libs/billing/src"], + "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"], + "@bitwarden/cli/*": ["./apps/cli/src/*"], + "@bitwarden/common/*": ["./libs/common/src/*"], + "@bitwarden/components": ["./libs/components/src"], + "@bitwarden/dirt-card": ["./libs/dirt/card/src"], + "@bitwarden/generator-components": ["./libs/tools/generator/components/src"], + "@bitwarden/generator-core": ["./libs/tools/generator/core/src"], + "@bitwarden/generator-history": ["./libs/tools/generator/extensions/history/src"], + "@bitwarden/generator-legacy": ["./libs/tools/generator/extensions/legacy/src"], + "@bitwarden/generator-navigation": ["./libs/tools/generator/extensions/navigation/src"], + "@bitwarden/importer-core": ["./libs/importer/src"], + "@bitwarden/importer-ui": ["./libs/importer/src/components"], + "@bitwarden/key-management": ["./libs/key-management/src"], + "@bitwarden/key-management-ui": ["./libs/key-management-ui/src"], + "@bitwarden/node/*": ["./libs/node/src/*"], + "@bitwarden/platform": ["./libs/platform/src"], + "@bitwarden/platform/*": ["./libs/platform/src/*"], + "@bitwarden/send-ui": ["./libs/tools/send/send-ui/src"], + "@bitwarden/ui-common": ["./libs/ui/common/src"], + "@bitwarden/ui-common/setup-jest": ["./libs/ui/common/src/setup-jest"], + "@bitwarden/vault-export-core": ["./libs/tools/export/vault-export/vault-export-core/src"], + "@bitwarden/vault-export-ui": ["./libs/tools/export/vault-export/vault-export-ui/src"], + "@bitwarden/vault": ["./libs/vault/src"], + "@bitwarden/web-vault/*": ["./apps/web/src/*"] + }, + "plugins": [ + { + "name": "typescript-strict-plugin" + } + ], + "useDefineForClassFields": false + } +} diff --git a/tsconfig.json b/tsconfig.json index 525af0ac3b7..35200efa430 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,57 +1,5 @@ { - "compilerOptions": { - "strict": false, - "pretty": true, - "moduleResolution": "node", - "noImplicitAny": true, - "target": "ES2016", - "module": "ES2020", - "lib": ["es5", "es6", "es7", "dom", "ES2021", "ESNext.Disposable"], - "sourceMap": true, - "allowSyntheticDefaultImports": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "declaration": false, - "outDir": "dist", - "baseUrl": ".", - "resolveJsonModule": true, - "paths": { - "@bitwarden/admin-console/common": ["./libs/admin-console/src/common"], - "@bitwarden/angular/*": ["./libs/angular/src/*"], - "@bitwarden/auth/angular": ["./libs/auth/src/angular"], - "@bitwarden/auth/common": ["./libs/auth/src/common"], - "@bitwarden/billing": ["./libs/billing/src"], - "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"], - "@bitwarden/common/*": ["./libs/common/src/*"], - "@bitwarden/components": ["./libs/components/src"], - "@bitwarden/dirt-card": ["./libs/dirt/card/src"], - "@bitwarden/generator-components": ["./libs/tools/generator/components/src"], - "@bitwarden/generator-core": ["./libs/tools/generator/core/src"], - "@bitwarden/generator-history": ["./libs/tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["./libs/tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["./libs/tools/generator/extensions/navigation/src"], - "@bitwarden/importer-core": ["./libs/importer/src"], - "@bitwarden/importer-ui": ["./libs/importer/src/components"], - "@bitwarden/key-management": ["./libs/key-management/src"], - "@bitwarden/key-management-ui": ["./libs/key-management-ui/src"], - "@bitwarden/node/*": ["./libs/node/src/*"], - "@bitwarden/platform": ["./libs/platform/src"], - "@bitwarden/platform/*": ["./libs/platform/src/*"], - "@bitwarden/send-ui": ["./libs/tools/send/send-ui/src"], - "@bitwarden/ui-common": ["./libs/ui/common/src"], - "@bitwarden/ui-common/setup-jest": ["./libs/ui/common/src/setup-jest"], - "@bitwarden/vault-export-core": ["./libs/tools/export/vault-export/vault-export-core/src"], - "@bitwarden/vault-export-ui": ["./libs/tools/export/vault-export/vault-export-ui/src"], - "@bitwarden/vault": ["./libs/vault/src"], - "@bitwarden/web-vault/*": ["./apps/web/src/*"] - }, - "plugins": [ - { - "name": "typescript-strict-plugin" - } - ], - "useDefineForClassFields": false - }, + "extends": "./tsconfig.base.json", "include": [ "apps/web/src/**/*", "apps/browser/src/**/*", From 23ec6bacc9a992a4e2e1eb9aeff8a61dcfb60bfc Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Mon, 2 Jun 2025 23:56:29 +0200 Subject: [PATCH 221/245] [PM-20225] Prevent legacy users without userkey from logging in (#14267) * Prevent legacy users without userkey from logging in * Remove further web-migration code for legacy users * Add i18n for legacy user error message * Update comment * Remove migrate legacy component * Remove i18n messages * Remove migrate legacy encryption reference --- apps/browser/src/_locales/en/messages.json | 4 +- apps/cli/src/auth/commands/login.command.ts | 6 +- apps/cli/src/locales/en/messages.json | 3 + apps/cli/src/program.ts | 1 + apps/desktop/src/locales/en/messages.json | 4 +- .../core/services/two-factor-auth/index.ts | 1 - .../web-two-factor-auth-component.service.ts | 14 --- apps/web/src/app/core/core.module.ts | 8 -- .../migrate-legacy-encryption.component.html | 36 ------- .../migrate-legacy-encryption.component.ts | 100 ------------------ apps/web/src/app/oss-routing.module.ts | 7 -- apps/web/src/locales/en/messages.json | 18 +--- .../src/auth/guards/lock.guard.spec.ts | 13 --- libs/angular/src/auth/guards/lock.guard.ts | 8 -- .../auth/src/angular/login/login.component.ts | 16 ++- ...fault-two-factor-auth-component.service.ts | 5 - .../two-factor-auth-component.service.ts | 19 ---- .../two-factor-auth.component.ts | 21 +--- .../common/login-strategies/login.strategy.ts | 8 +- 19 files changed, 27 insertions(+), 265 deletions(-) delete mode 100644 apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-component.service.ts delete mode 100644 apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.html delete mode 100644 apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 29223942fd6..68f303dd538 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1365,8 +1365,8 @@ "featureUnavailable": { "message": "Feature unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium membership" diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index cd5c8ef9bcd..a8e525e2206 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -34,6 +34,7 @@ import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/a import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -77,6 +78,7 @@ export class LoginCommand { protected logoutCallback: () => Promise<void>, protected kdfConfigService: KdfConfigService, protected ssoUrlService: SsoUrlService, + protected i18nService: I18nService, protected masterPasswordService: MasterPasswordServiceAbstraction, ) {} @@ -227,9 +229,7 @@ export class LoginCommand { ); } if (response.requiresEncryptionKeyMigration) { - return Response.error( - "Encryption key migration required. Please login through the web vault to update your encryption key.", - ); + return Response.error(this.i18nService.t("legacyEncryptionUnsupported")); } if (response.requiresTwoFactor) { const twoFactorProviders = await this.twoFactorService.getSupportedProviders(null); diff --git a/apps/cli/src/locales/en/messages.json b/apps/cli/src/locales/en/messages.json index 9149e25c5bc..815939c0c95 100644 --- a/apps/cli/src/locales/en/messages.json +++ b/apps/cli/src/locales/en/messages.json @@ -185,6 +185,9 @@ } } }, + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." + }, "organizationUsingKeyConnectorOptInLoggedOut": { "message": "An organization you are a member of is using Key Connector. In order to access the vault, you must opt-in to Key Connector now via the web vault. You have been logged out." }, diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index e7e25d66343..468901282b4 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -175,6 +175,7 @@ export class Program extends BaseProgram { async () => await this.serviceContainer.logout(), this.serviceContainer.kdfConfigService, this.serviceContainer.ssoUrlService, + this.serviceContainer.i18nService, this.serviceContainer.masterPasswordService, ); const response = await command.run(email, password, options); diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 9d668d464ae..0cc466196fb 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -691,8 +691,8 @@ "maxFileSize": { "message": "Maximum file size is 500 MB." }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "editedFolder": { "message": "Folder saved" diff --git a/apps/web/src/app/auth/core/services/two-factor-auth/index.ts b/apps/web/src/app/auth/core/services/two-factor-auth/index.ts index ba2697fdee4..4ca57b34737 100644 --- a/apps/web/src/app/auth/core/services/two-factor-auth/index.ts +++ b/apps/web/src/app/auth/core/services/two-factor-auth/index.ts @@ -1,2 +1 @@ -export * from "./web-two-factor-auth-component.service"; export * from "./web-two-factor-auth-duo-component.service"; diff --git a/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-component.service.ts b/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-component.service.ts deleted file mode 100644 index 451cec57ddd..00000000000 --- a/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-component.service.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { - DefaultTwoFactorAuthComponentService, - TwoFactorAuthComponentService, - LegacyKeyMigrationAction, -} from "@bitwarden/auth/angular"; - -export class WebTwoFactorAuthComponentService - extends DefaultTwoFactorAuthComponentService - implements TwoFactorAuthComponentService -{ - override determineLegacyKeyMigrationAction(): LegacyKeyMigrationAction { - return LegacyKeyMigrationAction.NAVIGATE_TO_MIGRATION_COMPONENT; - } -} diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index e812edd8f32..46435981a5e 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -32,7 +32,6 @@ import { SetPasswordJitService, SsoComponentService, LoginDecryptionOptionsService, - TwoFactorAuthComponentService, TwoFactorAuthDuoComponentService, ChangePasswordService, } from "@bitwarden/auth/angular"; @@ -116,7 +115,6 @@ import { WebRegistrationFinishService, WebLoginComponentService, WebLoginDecryptionOptionsService, - WebTwoFactorAuthComponentService, WebTwoFactorAuthDuoComponentService, LinkSsoService, } from "../auth"; @@ -269,12 +267,6 @@ const safeProviders: SafeProvider[] = [ useClass: WebLockComponentService, deps: [], }), - // TODO: PM-18182 - Refactor component services into lazy loaded modules - safeProvider({ - provide: TwoFactorAuthComponentService, - useClass: WebTwoFactorAuthComponentService, - deps: [], - }), safeProvider({ provide: SetPasswordJitService, useClass: WebSetPasswordJitService, diff --git a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.html b/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.html deleted file mode 100644 index 7ed1efeb461..00000000000 --- a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.html +++ /dev/null @@ -1,36 +0,0 @@ -<form [formGroup]="formGroup" [bitSubmit]="submit"> - <div class="tw-mt-12 tw-flex tw-justify-center"> - <div class="tw-max-w-xl"> - <h1 bitTypography="h1" class="tw-mb-4 tw-text-center">{{ "updateEncryptionKey" | i18n }}</h1> - <div - class="tw-block tw-rounded tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-8" - > - <p> - {{ "updateEncryptionSchemeDesc" | i18n }} - <a - href="https://bitwarden.com/help/account-encryption-key/#rotate-your-encryption-key" - target="_blank" - rel="noreferrer" - >{{ "learnMore" | i18n }}</a - > - </p> - <bit-callout type="warning">{{ "updateEncryptionKeyWarning" | i18n }}</bit-callout> - - <bit-form-field> - <bit-label>{{ "masterPass" | i18n }}</bit-label> - <input - id="masterPassword" - bitInput - type="password" - formControlName="masterPassword" - appAutofocus - /> - <button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button> - </bit-form-field> - <button type="submit" bitButton bitFormButton buttonType="primary" block> - {{ "updateEncryptionKey" | i18n }} - </button> - </div> - </div> - </div> -</form> diff --git a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts b/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts deleted file mode 100644 index f6685a749a2..00000000000 --- a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Component } from "@angular/core"; -import { FormControl, FormGroup, Validators } from "@angular/forms"; -import { firstValueFrom } from "rxjs"; - -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; - -import { SharedModule } from "../../shared"; -import { UserKeyRotationModule } from "../key-rotation/user-key-rotation.module"; -import { UserKeyRotationService } from "../key-rotation/user-key-rotation.service"; - -// The master key was originally used to encrypt user data, before the user key was introduced. -// This component is used to migrate from the old encryption scheme to the new one. -@Component({ - imports: [SharedModule, UserKeyRotationModule], - templateUrl: "migrate-legacy-encryption.component.html", -}) -export class MigrateFromLegacyEncryptionComponent { - protected formGroup = new FormGroup({ - masterPassword: new FormControl("", [Validators.required]), - }); - - constructor( - private accountService: AccountService, - private keyRotationService: UserKeyRotationService, - private i18nService: I18nService, - private keyService: KeyService, - private messagingService: MessagingService, - private logService: LogService, - private syncService: SyncService, - private toastService: ToastService, - private dialogService: DialogService, - private folderApiService: FolderApiServiceAbstraction, - ) {} - - submit = async () => { - this.formGroup.markAsTouched(); - - if (this.formGroup.invalid) { - return; - } - - const activeUser = await firstValueFrom(this.accountService.activeAccount$); - if (activeUser == null) { - throw new Error("No active user."); - } - - const hasUserKey = await this.keyService.hasUserKey(activeUser.id); - if (hasUserKey) { - this.messagingService.send("logout"); - throw new Error("User key already exists, cannot migrate legacy encryption."); - } - - const masterPassword = this.formGroup.value.masterPassword!; - - try { - await this.syncService.fullSync(false, true); - - await this.keyRotationService.rotateUserKeyAndEncryptedDataLegacy(masterPassword, activeUser); - - this.toastService.showToast({ - variant: "success", - title: this.i18nService.t("keyUpdated"), - message: this.i18nService.t("logBackInOthersToo"), - timeout: 15000, - }); - this.messagingService.send("logout"); - } catch (e) { - // If the error is due to missing folders, we can delete all folders and try again - if ( - e instanceof ErrorResponse && - e.message === "All existing folders must be included in the rotation." - ) { - const deleteFolders = await this.dialogService.openSimpleDialog({ - type: "warning", - title: { key: "encryptionKeyUpdateCannotProceed" }, - content: { key: "keyUpdateFoldersFailed" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: { key: "cancel" }, - }); - - if (deleteFolders) { - await this.folderApiService.deleteAll(activeUser.id); - await this.syncService.fullSync(true, true); - await this.submit(); - return; - } - } - this.logService.error(e); - throw e; - } - }; -} diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 0d6ffb88ad6..6a7cc51d3ba 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -151,13 +151,6 @@ const routes: Routes = [ canActivate: [authGuard], data: { titleId: "updatePassword" } satisfies RouteDataProperties, }, - { - path: "migrate-legacy-encryption", - loadComponent: () => - import("./key-management/migrate-encryption/migrate-legacy-encryption.component").then( - (mod) => mod.MigrateFromLegacyEncryptionComponent, - ), - }, ], }, { diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index e1a2d3cbef2..a217b38e650 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -4473,9 +4473,6 @@ } } }, - "encryptionKeyUpdateCannotProceed": { - "message": "Encryption key update cannot proceed" - }, "editFieldLabel": { "message": "Edit $LABEL$", "placeholders": { @@ -4528,24 +4525,15 @@ } } }, - "keyUpdateFoldersFailed": { - "message": "When updating your encryption key, your folders could not be decrypted. To continue with the update, your folders must be deleted. No vault items will be deleted if you proceed." - }, - "keyUpdated": { - "message": "Key updated" - }, - "updateEncryptionKey": { - "message": "Update encryption key" - }, - "updateEncryptionSchemeDesc": { - "message": "We've changed the encryption scheme to provide better security. Update your encryption key now by entering your master password below." - }, "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, "updateEncryptionKeyAccountExportWarning": { "message": "Any account restricted exports you have saved will become invalid." }, + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." + }, "subscription": { "message": "Subscription" }, diff --git a/libs/angular/src/auth/guards/lock.guard.spec.ts b/libs/angular/src/auth/guards/lock.guard.spec.ts index ed77f9bdebf..2085e0f3486 100644 --- a/libs/angular/src/auth/guards/lock.guard.spec.ts +++ b/libs/angular/src/auth/guards/lock.guard.spec.ts @@ -79,7 +79,6 @@ describe("lockGuard", () => { { path: "", component: EmptyComponent }, { path: "lock", component: EmptyComponent, canActivate: [lockGuard()] }, { path: "non-lock-route", component: EmptyComponent }, - { path: "migrate-legacy-encryption", component: EmptyComponent }, ]), ], providers: [ @@ -182,18 +181,6 @@ describe("lockGuard", () => { expect(messagingService.send).toHaveBeenCalledWith("logout"); }); - it("should send the user to migrate-legacy-encryption if they are a legacy user on a web client", async () => { - const { router } = setup({ - authStatus: AuthenticationStatus.Locked, - canLock: true, - isLegacyUser: true, - clientType: ClientType.Web, - }); - - await router.navigate(["lock"]); - expect(router.url).toBe("/migrate-legacy-encryption"); - }); - it("should allow navigation to the lock route when device trust is supported, the user has a MP, and the user is coming from the login-initiated page", async () => { const { router } = setup({ authStatus: AuthenticationStatus.Locked, diff --git a/libs/angular/src/auth/guards/lock.guard.ts b/libs/angular/src/auth/guards/lock.guard.ts index 01d03dc718d..4b09ddeee18 100644 --- a/libs/angular/src/auth/guards/lock.guard.ts +++ b/libs/angular/src/auth/guards/lock.guard.ts @@ -11,11 +11,9 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { ClientType } from "@bitwarden/common/enums"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { KeyService } from "@bitwarden/key-management"; /** @@ -33,7 +31,6 @@ export function lockGuard(): CanActivateFn { const authService = inject(AuthService); const keyService = inject(KeyService); const deviceTrustService = inject(DeviceTrustServiceAbstraction); - const platformUtilService = inject(PlatformUtilsService); const messagingService = inject(MessagingService); const router = inject(Router); const userVerificationService = inject(UserVerificationService); @@ -59,12 +56,7 @@ export function lockGuard(): CanActivateFn { return false; } - // If legacy user on web, redirect to migration page if (await keyService.isLegacyUser()) { - if (platformUtilService.getClientType() === ClientType.Web) { - return router.createUrlTree(["migrate-legacy-encryption"]); - } - // Log out legacy users on other clients messagingService.send("logout"); return false; } diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index 8674453cf10..425260ec2e0 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -282,16 +282,12 @@ export class LoginComponent implements OnInit, OnDestroy { private async handleAuthResult(authResult: AuthResult): Promise<void> { if (authResult.requiresEncryptionKeyMigration) { /* Legacy accounts used the master key to encrypt data. - Migration is required but only performed on Web. */ - if (this.clientType === ClientType.Web) { - await this.router.navigate(["migrate-legacy-encryption"]); - } else { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccured"), - message: this.i18nService.t("encryptionKeyMigrationRequired"), - }); - } + This is now unsupported and requires a downgraded client */ + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccured"), + message: this.i18nService.t("legacyEncryptionUnsupported"), + }); return; } diff --git a/libs/auth/src/angular/two-factor-auth/default-two-factor-auth-component.service.ts b/libs/auth/src/angular/two-factor-auth/default-two-factor-auth-component.service.ts index f68c1d34515..1ce0cba5afb 100644 --- a/libs/auth/src/angular/two-factor-auth/default-two-factor-auth-component.service.ts +++ b/libs/auth/src/angular/two-factor-auth/default-two-factor-auth-component.service.ts @@ -1,6 +1,5 @@ import { DuoLaunchAction, - LegacyKeyMigrationAction, TwoFactorAuthComponentService, } from "./two-factor-auth-component.service"; @@ -9,10 +8,6 @@ export class DefaultTwoFactorAuthComponentService implements TwoFactorAuthCompon return false; } - determineLegacyKeyMigrationAction() { - return LegacyKeyMigrationAction.PREVENT_LOGIN_AND_SHOW_REQUIRE_MIGRATION_WARNING; - } - determineDuoLaunchAction(): DuoLaunchAction { return DuoLaunchAction.DIRECT_LAUNCH; } diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts index c99722fb8e4..2d2cdba3a10 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts @@ -1,12 +1,5 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; -// FIXME: update to use a const object instead of a typescript enum -// eslint-disable-next-line @bitwarden/platform/no-enums -export enum LegacyKeyMigrationAction { - PREVENT_LOGIN_AND_SHOW_REQUIRE_MIGRATION_WARNING, - NAVIGATE_TO_MIGRATION_COMPONENT, -} - // FIXME: update to use a const object instead of a typescript enum // eslint-disable-next-line @bitwarden/platform/no-enums export enum DuoLaunchAction { @@ -38,18 +31,6 @@ export abstract class TwoFactorAuthComponentService { */ abstract removePopupWidthExtension?(): void; - /** - * We used to use the user's master key to encrypt their data. We deprecated that approach - * and now use a user key. This method should be called if we detect that the user - * is still using the old master key encryption scheme (server sends down a flag to - * indicate this). This method then determines what action to take based on the client. - * - * We have two possible actions: - * 1. Prevent the user from logging in and show a warning that they need to migrate their key on the web client today. - * 2. Navigate the user to the key migration component on the web client. - */ - abstract determineLegacyKeyMigrationAction(): LegacyKeyMigrationAction; - /** * Optionally closes any single action popouts (extension only). * @returns true if we are in a single action popout and it was closed, false otherwise. diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 57637fe9118..85184283efd 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -69,7 +69,6 @@ import { } from "./two-factor-auth-component-cache.service"; import { DuoLaunchAction, - LegacyKeyMigrationAction, TwoFactorAuthComponentService, } from "./two-factor-auth-component.service"; import { @@ -388,22 +387,12 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { if (!result.requiresEncryptionKeyMigration) { return false; } - // Migration is forced so prevent login via return - const legacyKeyMigrationAction: LegacyKeyMigrationAction = - this.twoFactorAuthComponentService.determineLegacyKeyMigrationAction(); - switch (legacyKeyMigrationAction) { - case LegacyKeyMigrationAction.NAVIGATE_TO_MIGRATION_COMPONENT: - await this.router.navigate(["migrate-legacy-encryption"]); - break; - case LegacyKeyMigrationAction.PREVENT_LOGIN_AND_SHOW_REQUIRE_MIGRATION_WARNING: - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccured"), - message: this.i18nService.t("encryptionKeyMigrationRequired"), - }); - break; - } + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccured"), + message: this.i18nService.t("legacyEncryptionUnsupported"), + }); return true; } diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index 6e66d65b654..f1b7d236fb7 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -17,7 +17,6 @@ import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/model import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { ClientType } from "@bitwarden/common/enums"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { @@ -254,13 +253,10 @@ export abstract class LoginStrategy { protected async processTokenResponse(response: IdentityTokenResponse): Promise<AuthResult> { const result = new AuthResult(); - // Old encryption keys must be migrated, but is currently only available on web. - // Other clients shouldn't continue the login process. + // Encryption key migration of legacy users (with no userkey) is not supported anymore if (this.encryptionKeyMigrationRequired(response)) { result.requiresEncryptionKeyMigration = true; - if (this.platformUtilsService.getClientType() !== ClientType.Web) { - return result; - } + return result; } // Must come before setting keys, user key needs email to update additional keys. From 3cad691f13ccbeb9baba6dbfbb0bc7b1af906e06 Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Tue, 3 Jun 2025 00:51:36 +0200 Subject: [PATCH 222/245] Remove standalone true from ac (#15036) Remove standalone: true from every instance since it's the default as of Angular 19. --- .../bulk-collections-dialog/bulk-collections-dialog.component.ts | 1 - .../collections/collection-access-restricted.component.ts | 1 - .../collection-badge/collection-name.badge.component.ts | 1 - .../organizations/collections/pipes/get-collection-name.pipe.ts | 1 - .../collections/vault-header/vault-header.component.ts | 1 - .../admin-console/organizations/collections/vault.component.ts | 1 - .../organizations/integrations/integrations.component.ts | 1 - .../organizations/layouts/organization-layout.component.ts | 1 - .../organizations/manage/entity-events.component.ts | 1 - .../organizations/manage/verify-recover-delete-org.component.ts | 1 - .../settings/components/delete-organization-dialog.component.ts | 1 - .../components/collection-dialog/collection-dialog.component.ts | 1 - .../integrations/integration-card/integration-card.component.ts | 1 - .../integrations/integration-grid/integration-grid.component.ts | 1 - .../shared/components/integrations/integrations.pipe.ts | 1 - .../sponsorships/families-for-enterprise-setup.component.ts | 1 - .../app/admin-console/settings/create-organization.component.ts | 1 - .../manage/device-approvals/device-approvals.component.ts | 1 - .../src/app/admin-console/providers/clients/clients.component.ts | 1 - .../app/admin-console/providers/providers-layout.component.ts | 1 - 20 files changed, 20 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts b/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts index 147340e6a00..7c4e2156ffb 100644 --- a/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts @@ -54,7 +54,6 @@ export enum BulkCollectionsDialogResult { imports: [SharedModule, AccessSelectorModule], selector: "app-bulk-collections-dialog", templateUrl: "bulk-collections-dialog.component.html", - standalone: true, }) export class BulkCollectionsDialogComponent implements OnDestroy { protected readonly PermissionMode = PermissionMode; diff --git a/apps/web/src/app/admin-console/organizations/collections/collection-access-restricted.component.ts b/apps/web/src/app/admin-console/organizations/collections/collection-access-restricted.component.ts index 15ba10a0d59..3f26e03e203 100644 --- a/apps/web/src/app/admin-console/organizations/collections/collection-access-restricted.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/collection-access-restricted.component.ts @@ -12,7 +12,6 @@ const icon = svgIcon`<svg xmlns="http://www.w3.org/2000/svg" width="120" height= @Component({ selector: "collection-access-restricted", - standalone: true, imports: [SharedModule, ButtonModule, NoItemsModule], template: `<bit-no-items [icon]="icon" class="tw-mt-2 tw-block"> <span slot="title" class="tw-mt-4 tw-block">{{ "youDoNotHavePermissions" | i18n }}</span> diff --git a/apps/web/src/app/admin-console/organizations/collections/collection-badge/collection-name.badge.component.ts b/apps/web/src/app/admin-console/organizations/collections/collection-badge/collection-name.badge.component.ts index d8ace8acc56..728faaf66e2 100644 --- a/apps/web/src/app/admin-console/organizations/collections/collection-badge/collection-name.badge.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/collection-badge/collection-name.badge.component.ts @@ -10,7 +10,6 @@ import { GetCollectionNameFromIdPipe } from "../pipes"; @Component({ selector: "app-collection-badge", templateUrl: "collection-name-badge.component.html", - standalone: true, imports: [SharedModule, GetCollectionNameFromIdPipe], }) export class CollectionNameBadgeComponent { diff --git a/apps/web/src/app/admin-console/organizations/collections/pipes/get-collection-name.pipe.ts b/apps/web/src/app/admin-console/organizations/collections/pipes/get-collection-name.pipe.ts index 8833ddfa382..b52719304b8 100644 --- a/apps/web/src/app/admin-console/organizations/collections/pipes/get-collection-name.pipe.ts +++ b/apps/web/src/app/admin-console/organizations/collections/pipes/get-collection-name.pipe.ts @@ -5,7 +5,6 @@ import { CollectionView } from "@bitwarden/admin-console/common"; @Pipe({ name: "collectionNameFromId", pure: true, - standalone: true, }) export class GetCollectionNameFromIdPipe implements PipeTransform { transform(value: string, collections: CollectionView[]) { diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts index 4c129e325c5..b343d5874bc 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts @@ -35,7 +35,6 @@ import { import { CollectionDialogTabType } from "../../shared/components/collection-dialog"; @Component({ - standalone: true, selector: "app-org-vault-header", templateUrl: "./vault-header.component.html", imports: [ diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts index 45300b45fa5..bc0f517d1fb 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts @@ -144,7 +144,6 @@ enum AddAccessStatusType { } @Component({ - standalone: true, selector: "app-org-vault", templateUrl: "vault.component.html", imports: [ diff --git a/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts b/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts index 80c12af8522..e6a62b1db73 100644 --- a/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts +++ b/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts @@ -22,7 +22,6 @@ import { Integration } from "../shared/components/integrations/models"; @Component({ selector: "ac-integrations", templateUrl: "./integrations.component.html", - standalone: true, imports: [ SharedModule, SharedOrganizationModule, diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts index 726832b478a..e60cc918fb8 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts @@ -37,7 +37,6 @@ import { AdminConsoleLogo } from "../../icons/admin-console-logo"; @Component({ selector: "app-organization-layout", templateUrl: "organization-layout.component.html", - standalone: true, imports: [ CommonModule, RouterModule, diff --git a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts index 4eab2969fff..10f68695e88 100644 --- a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts @@ -38,7 +38,6 @@ export interface EntityEventsDialogParams { @Component({ imports: [SharedModule], templateUrl: "entity-events.component.html", - standalone: true, }) export class EntityEventsComponent implements OnInit, OnDestroy { loading = true; diff --git a/apps/web/src/app/admin-console/organizations/manage/verify-recover-delete-org.component.ts b/apps/web/src/app/admin-console/organizations/manage/verify-recover-delete-org.component.ts index 6dcdff00160..f88eb82e529 100644 --- a/apps/web/src/app/admin-console/organizations/manage/verify-recover-delete-org.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/verify-recover-delete-org.component.ts @@ -14,7 +14,6 @@ import { SharedModule } from "../../../shared/shared.module"; @Component({ templateUrl: "verify-recover-delete-org.component.html", - standalone: true, imports: [SharedModule], }) export class VerifyRecoverDeleteOrgComponent implements OnInit { diff --git a/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts b/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts index e942eecbd37..8c2bfe079de 100644 --- a/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts @@ -80,7 +80,6 @@ export enum DeleteOrganizationDialogResult { @Component({ selector: "app-delete-organization", - standalone: true, imports: [SharedModule, UserVerificationModule], templateUrl: "delete-organization-dialog.component.html", }) diff --git a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts index 70b26041df6..e9865f14d54 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts @@ -117,7 +117,6 @@ export enum CollectionDialogAction { @Component({ templateUrl: "collection-dialog.component.html", - standalone: true, imports: [SharedModule, AccessSelectorModule, SelectModule], }) export class CollectionDialogComponent implements OnInit, OnDestroy { diff --git a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.ts index 3943ceb22ed..20e4028e9df 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.ts @@ -20,7 +20,6 @@ import { SharedModule } from "../../../../../../shared/shared.module"; @Component({ selector: "app-integration-card", templateUrl: "./integration-card.component.html", - standalone: true, imports: [SharedModule], }) export class IntegrationCardComponent implements AfterViewInit, OnDestroy { diff --git a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.ts index 2e3158f9894..55b552bd251 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.ts @@ -11,7 +11,6 @@ import { Integration } from "../models"; @Component({ selector: "app-integration-grid", templateUrl: "./integration-grid.component.html", - standalone: true, imports: [IntegrationCardComponent, SharedModule], }) export class IntegrationGridComponent { diff --git a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integrations.pipe.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integrations.pipe.ts index 760d9913e9e..ae9f73e78c0 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integrations.pipe.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integrations.pipe.ts @@ -6,7 +6,6 @@ import { Integration } from "../../../shared/components/integrations/models"; @Pipe({ name: "filterIntegrations", - standalone: true, }) export class FilterIntegrationsPipe implements PipeTransform { transform(integrations: Integration[], type: IntegrationType): Integration[] { diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts index 57fe212fa65..30c0ba159c1 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts @@ -30,7 +30,6 @@ import { @Component({ templateUrl: "families-for-enterprise-setup.component.html", - standalone: true, imports: [SharedModule, OrganizationPlansComponent], }) export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { diff --git a/apps/web/src/app/admin-console/settings/create-organization.component.ts b/apps/web/src/app/admin-console/settings/create-organization.component.ts index 7a20826086d..f87e9ec5b72 100644 --- a/apps/web/src/app/admin-console/settings/create-organization.component.ts +++ b/apps/web/src/app/admin-console/settings/create-organization.component.ts @@ -13,7 +13,6 @@ import { SharedModule } from "../../shared"; @Component({ templateUrl: "create-organization.component.html", - standalone: true, imports: [SharedModule, OrganizationPlansComponent, HeaderModule], }) export class CreateOrganizationComponent { diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts index 83f23089c59..08c7f181308 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts @@ -26,7 +26,6 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared/shared.module"; @Component({ selector: "app-org-device-approvals", templateUrl: "./device-approvals.component.html", - standalone: true, providers: [ safeProvider({ provide: OrganizationAuthRequestApiService, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts index f830b149db4..130f1f2c482 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts @@ -43,7 +43,6 @@ const DisallowedPlanTypes = [ @Component({ templateUrl: "clients.component.html", - standalone: true, imports: [ SharedOrganizationModule, HeaderModule, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts index bbd25d6dbe2..72d87136f55 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts @@ -18,7 +18,6 @@ import { WebLayoutModule } from "@bitwarden/web-vault/app/layouts/web-layout.mod @Component({ selector: "providers-layout", templateUrl: "providers-layout.component.html", - standalone: true, imports: [CommonModule, RouterModule, JslibModule, WebLayoutModule, IconModule], }) export class ProvidersLayoutComponent implements OnInit, OnDestroy { From 95856bf3cf1f20ce303298f043469c6201d550a7 Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Tue, 3 Jun 2025 09:55:58 +0200 Subject: [PATCH 223/245] [CL-714] Remove standalone true from tools (#15039) Remove standalone: true from every instance since it's the default as of Angular 19. --- .../src/tools/popup/components/file-popout-callout.component.ts | 1 - .../popup/generator/credential-generator-history.component.ts | 1 - .../src/tools/popup/generator/credential-generator.component.ts | 1 - .../src/tools/popup/send-v2/add-edit/send-add-edit.component.ts | 1 - .../tools/popup/send-v2/send-created/send-created.component.ts | 1 - .../send-file-popout-dialog-container.component.ts | 1 - .../send-file-popout-dialog/send-file-popout-dialog.component.ts | 1 - apps/browser/src/tools/popup/send-v2/send-v2.component.ts | 1 - .../tools/popup/settings/about-dialog/about-dialog.component.ts | 1 - .../tools/popup/settings/about-page/about-page-v2.component.ts | 1 - .../tools/popup/settings/export/export-browser-v2.component.ts | 1 - .../tools/popup/settings/import/import-browser-v2.component.ts | 1 - apps/browser/src/tools/popup/settings/settings-v2.component.ts | 1 - apps/desktop/src/app/tools/export/export-desktop.component.ts | 1 - .../src/app/tools/generator/credential-generator.component.ts | 1 - apps/desktop/src/app/tools/import/import-desktop.component.ts | 1 - apps/desktop/src/app/tools/send/add-edit.component.ts | 1 - apps/desktop/src/app/tools/send/send.component.ts | 1 - .../tools/credential-generator/credential-generator.component.ts | 1 - apps/web/src/app/tools/import/import-web.component.ts | 1 - apps/web/src/app/tools/import/org-import.component.ts | 1 - .../src/app/tools/send/new-send/new-send-dropdown.component.ts | 1 - apps/web/src/app/tools/send/send-access/access.component.ts | 1 - .../tools/send/send-access/send-access-explainer.component.ts | 1 - .../src/app/tools/send/send-access/send-access-file.component.ts | 1 - .../app/tools/send/send-access/send-access-password.component.ts | 1 - .../src/app/tools/send/send-access/send-access-text.component.ts | 1 - apps/web/src/app/tools/send/send.component.ts | 1 - apps/web/src/app/tools/vault-export/export-web.component.ts | 1 - .../web/src/app/tools/vault-export/org-vault-export.component.ts | 1 - .../tools/password-strength/password-strength-v2.component.ts | 1 - .../src/components/dialog/file-password-prompt.component.ts | 1 - .../src/components/dialog/import-error-dialog.component.ts | 1 - .../src/components/dialog/import-success-dialog.component.ts | 1 - .../src/components/dialog/sshkey-password-prompt.component.ts | 1 - libs/importer/src/components/import.component.ts | 1 - .../lastpass/dialog/lastpass-multifactor-prompt.component.ts | 1 - .../lastpass/dialog/lastpass-password-prompt.component.ts | 1 - .../src/components/lastpass/import-lastpass.component.ts | 1 - .../src/components/export-scope-callout.component.ts | 1 - .../vault-export-ui/src/components/export.component.ts | 1 - .../src/credential-generator-history-dialog.component.ts | 1 - .../components/src/credential-generator-history.component.ts | 1 - .../components/src/empty-credential-history.component.ts | 1 - .../components/src/nudge-generator-spotlight.component.ts | 1 - .../send/send-ui/src/add-edit/send-add-edit-dialog.component.ts | 1 - .../send-ui/src/new-send-dropdown/new-send-dropdown.component.ts | 1 - .../src/send-form/components/options/send-options.component.ts | 1 - .../send-form/components/send-details/send-details.component.ts | 1 - .../components/send-details/send-file-details.component.ts | 1 - .../components/send-details/send-text-details.component.ts | 1 - .../send/send-ui/src/send-form/components/send-form.component.ts | 1 - .../send-ui/src/send-list-filters/send-list-filters.component.ts | 1 - .../send-list-items-container.component.ts | 1 - libs/tools/send/send-ui/src/send-search/send-search.component.ts | 1 - 55 files changed, 55 deletions(-) diff --git a/apps/browser/src/tools/popup/components/file-popout-callout.component.ts b/apps/browser/src/tools/popup/components/file-popout-callout.component.ts index 491e33c5738..e30fbf58321 100644 --- a/apps/browser/src/tools/popup/components/file-popout-callout.component.ts +++ b/apps/browser/src/tools/popup/components/file-popout-callout.component.ts @@ -12,7 +12,6 @@ import { FilePopoutUtilsService } from "../services/file-popout-utils.service"; @Component({ selector: "tools-file-popout-callout", templateUrl: "file-popout-callout.component.html", - standalone: true, imports: [CommonModule, JslibModule, CalloutModule], }) export class FilePopoutCalloutComponent implements OnInit { diff --git a/apps/browser/src/tools/popup/generator/credential-generator-history.component.ts b/apps/browser/src/tools/popup/generator/credential-generator-history.component.ts index 2bf290b3223..441e5d6e4c6 100644 --- a/apps/browser/src/tools/popup/generator/credential-generator-history.component.ts +++ b/apps/browser/src/tools/popup/generator/credential-generator-history.component.ts @@ -28,7 +28,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co @Component({ selector: "app-credential-generator-history", templateUrl: "credential-generator-history.component.html", - standalone: true, imports: [ ButtonModule, CommonModule, diff --git a/apps/browser/src/tools/popup/generator/credential-generator.component.ts b/apps/browser/src/tools/popup/generator/credential-generator.component.ts index a2ef4be6620..b34c829b006 100644 --- a/apps/browser/src/tools/popup/generator/credential-generator.component.ts +++ b/apps/browser/src/tools/popup/generator/credential-generator.component.ts @@ -11,7 +11,6 @@ import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-heade import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @Component({ - standalone: true, selector: "credential-generator", templateUrl: "credential-generator.component.html", imports: [ diff --git a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts index 0962dec3dcf..b6957248d75 100644 --- a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts +++ b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts @@ -63,7 +63,6 @@ export type AddEditQueryParams = Partial<Record<keyof QueryParams, string>>; @Component({ selector: "tools-send-add-edit", templateUrl: "send-add-edit.component.html", - standalone: true, providers: [{ provide: SendFormConfigService, useClass: DefaultSendFormConfigService }], imports: [ CommonModule, diff --git a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts index 7680e05dd5b..89d1ad5e809 100644 --- a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts @@ -23,7 +23,6 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page @Component({ selector: "app-send-created", templateUrl: "./send-created.component.html", - standalone: true, imports: [ ButtonModule, CommonModule, diff --git a/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.ts b/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.ts index 4266dd3914e..251f19cf252 100644 --- a/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.ts @@ -15,7 +15,6 @@ import { SendFilePopoutDialogComponent } from "./send-file-popout-dialog.compone @Component({ selector: "send-file-popout-dialog-container", templateUrl: "./send-file-popout-dialog-container.component.html", - standalone: true, imports: [JslibModule, CommonModule], }) export class SendFilePopoutDialogContainerComponent implements OnInit { diff --git a/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog.component.ts b/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog.component.ts index fb21b5bb026..248b3c49a98 100644 --- a/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog.component.ts @@ -9,7 +9,6 @@ import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; @Component({ selector: "send-file-popout-dialog", templateUrl: "./send-file-popout-dialog.component.html", - standalone: true, imports: [JslibModule, CommonModule, DialogModule, ButtonModule, TypographyModule], }) export class SendFilePopoutDialogComponent { diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts index 9fc19e98b34..e2b5551cc7d 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts @@ -35,7 +35,6 @@ export enum SendState { @Component({ templateUrl: "send-v2.component.html", - standalone: true, imports: [ CalloutModule, PopupPageComponent, diff --git a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts index 6f1c1162eb4..39bff089668 100644 --- a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts +++ b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts @@ -18,7 +18,6 @@ import { @Component({ templateUrl: "about-dialog.component.html", - standalone: true, imports: [CommonModule, JslibModule, DialogModule, ButtonModule, TypographyModule], }) export class AboutDialogComponent { diff --git a/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.ts b/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.ts index 51dbf3685ae..8a76290eff1 100644 --- a/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.ts @@ -33,7 +33,6 @@ const RateUrls = { @Component({ templateUrl: "about-page-v2.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.ts b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.ts index 27147b75d39..5aebee3b781 100644 --- a/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.ts @@ -14,7 +14,6 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page @Component({ templateUrl: "export-browser-v2.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts b/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts index 1c5558bd90e..506dae2fb18 100644 --- a/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts @@ -13,7 +13,6 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page @Component({ templateUrl: "import-browser-v2.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.ts b/apps/browser/src/tools/popup/settings/settings-v2.component.ts index 63a22f81ddd..a0383b99390 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.ts @@ -28,7 +28,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co @Component({ templateUrl: "settings-v2.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/apps/desktop/src/app/tools/export/export-desktop.component.ts b/apps/desktop/src/app/tools/export/export-desktop.component.ts index 11651111492..03afb154200 100644 --- a/apps/desktop/src/app/tools/export/export-desktop.component.ts +++ b/apps/desktop/src/app/tools/export/export-desktop.component.ts @@ -7,7 +7,6 @@ import { ExportComponent } from "@bitwarden/vault-export-ui"; @Component({ templateUrl: "export-desktop.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/apps/desktop/src/app/tools/generator/credential-generator.component.ts b/apps/desktop/src/app/tools/generator/credential-generator.component.ts index aed8bf18684..4124b2439da 100644 --- a/apps/desktop/src/app/tools/generator/credential-generator.component.ts +++ b/apps/desktop/src/app/tools/generator/credential-generator.component.ts @@ -14,7 +14,6 @@ import { } from "@bitwarden/generator-components"; @Component({ - standalone: true, selector: "credential-generator", templateUrl: "credential-generator.component.html", imports: [DialogModule, ButtonModule, JslibModule, GeneratorModule, ItemModule, LinkModule], diff --git a/apps/desktop/src/app/tools/import/import-desktop.component.ts b/apps/desktop/src/app/tools/import/import-desktop.component.ts index 7d0780bf0df..c1639c6d3ec 100644 --- a/apps/desktop/src/app/tools/import/import-desktop.component.ts +++ b/apps/desktop/src/app/tools/import/import-desktop.component.ts @@ -7,7 +7,6 @@ import { ImportComponent } from "@bitwarden/importer-ui"; @Component({ templateUrl: "import-desktop.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/apps/desktop/src/app/tools/send/add-edit.component.ts b/apps/desktop/src/app/tools/send/add-edit.component.ts index c0db3934259..025bab66539 100644 --- a/apps/desktop/src/app/tools/send/add-edit.component.ts +++ b/apps/desktop/src/app/tools/send/add-edit.component.ts @@ -22,7 +22,6 @@ import { CalloutModule, DialogService, ToastService } from "@bitwarden/component @Component({ selector: "app-send-add-edit", templateUrl: "add-edit.component.html", - standalone: true, imports: [CommonModule, JslibModule, ReactiveFormsModule, CalloutModule], }) export class AddEditComponent extends BaseAddEditComponent { diff --git a/apps/desktop/src/app/tools/send/send.component.ts b/apps/desktop/src/app/tools/send/send.component.ts index 6c2c3ed53c6..3ca26780853 100644 --- a/apps/desktop/src/app/tools/send/send.component.ts +++ b/apps/desktop/src/app/tools/send/send.component.ts @@ -38,7 +38,6 @@ const BroadcasterSubscriptionId = "SendComponent"; @Component({ selector: "app-send", templateUrl: "send.component.html", - standalone: true, imports: [CommonModule, JslibModule, FormsModule, NavComponent, AddEditComponent], }) export class SendComponent extends BaseSendComponent implements OnInit, OnDestroy { diff --git a/apps/web/src/app/tools/credential-generator/credential-generator.component.ts b/apps/web/src/app/tools/credential-generator/credential-generator.component.ts index 8d7b56a09ad..7d62bff0ac1 100644 --- a/apps/web/src/app/tools/credential-generator/credential-generator.component.ts +++ b/apps/web/src/app/tools/credential-generator/credential-generator.component.ts @@ -10,7 +10,6 @@ import { HeaderModule } from "../../layouts/header/header.module"; import { SharedModule } from "../../shared"; @Component({ - standalone: true, selector: "credential-generator", templateUrl: "credential-generator.component.html", imports: [SharedModule, HeaderModule, GeneratorModule, ButtonModule, LinkModule], diff --git a/apps/web/src/app/tools/import/import-web.component.ts b/apps/web/src/app/tools/import/import-web.component.ts index a527b9e71f4..7883769389f 100644 --- a/apps/web/src/app/tools/import/import-web.component.ts +++ b/apps/web/src/app/tools/import/import-web.component.ts @@ -8,7 +8,6 @@ import { SharedModule } from "../../shared"; @Component({ templateUrl: "import-web.component.html", - standalone: true, imports: [SharedModule, ImportComponent, HeaderModule], }) export class ImportWebComponent { diff --git a/apps/web/src/app/tools/import/org-import.component.ts b/apps/web/src/app/tools/import/org-import.component.ts index 90c13833ffc..fd833f3a698 100644 --- a/apps/web/src/app/tools/import/org-import.component.ts +++ b/apps/web/src/app/tools/import/org-import.component.ts @@ -20,7 +20,6 @@ import { ImportCollectionAdminService } from "./import-collection-admin.service" @Component({ templateUrl: "org-import.component.html", - standalone: true, imports: [SharedModule, ImportComponent, LooseComponentsModule], providers: [ { diff --git a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts index 8cd052aa016..64ada8f75d0 100644 --- a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts +++ b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts @@ -13,7 +13,6 @@ import { DefaultSendFormConfigService, SendAddEditDialogComponent } from "@bitwa @Component({ selector: "tools-new-send-dropdown", templateUrl: "new-send-dropdown.component.html", - standalone: true, imports: [JslibModule, CommonModule, ButtonModule, MenuModule, BadgeModule], providers: [DefaultSendFormConfigService], }) diff --git a/apps/web/src/app/tools/send/send-access/access.component.ts b/apps/web/src/app/tools/send/send-access/access.component.ts index 7fd66a10c20..2676cb9bef4 100644 --- a/apps/web/src/app/tools/send/send-access/access.component.ts +++ b/apps/web/src/app/tools/send/send-access/access.component.ts @@ -30,7 +30,6 @@ import { SendAccessTextComponent } from "./send-access-text.component"; @Component({ selector: "app-send-access", templateUrl: "access.component.html", - standalone: true, imports: [ SendAccessFileComponent, SendAccessTextComponent, diff --git a/apps/web/src/app/tools/send/send-access/send-access-explainer.component.ts b/apps/web/src/app/tools/send/send-access/send-access-explainer.component.ts index ec39d970444..d9f35a3d38e 100644 --- a/apps/web/src/app/tools/send/send-access/send-access-explainer.component.ts +++ b/apps/web/src/app/tools/send/send-access/send-access-explainer.component.ts @@ -5,7 +5,6 @@ import { SharedModule } from "../../../shared"; @Component({ selector: "app-send-access-explainer", templateUrl: "send-access-explainer.component.html", - standalone: true, imports: [SharedModule], }) export class SendAccessExplainerComponent { diff --git a/apps/web/src/app/tools/send/send-access/send-access-file.component.ts b/apps/web/src/app/tools/send/send-access/send-access-file.component.ts index eec0bfd787b..3b1bf427a0b 100644 --- a/apps/web/src/app/tools/send/send-access/send-access-file.component.ts +++ b/apps/web/src/app/tools/send/send-access/send-access-file.component.ts @@ -19,7 +19,6 @@ import { SharedModule } from "../../../shared"; selector: "app-send-access-file", templateUrl: "send-access-file.component.html", imports: [SharedModule], - standalone: true, }) export class SendAccessFileComponent { @Input() send: SendAccessView; diff --git a/apps/web/src/app/tools/send/send-access/send-access-password.component.ts b/apps/web/src/app/tools/send/send-access/send-access-password.component.ts index 0cfd93fcea0..81e66c8acc4 100644 --- a/apps/web/src/app/tools/send/send-access/send-access-password.component.ts +++ b/apps/web/src/app/tools/send/send-access/send-access-password.component.ts @@ -10,7 +10,6 @@ import { SharedModule } from "../../../shared"; selector: "app-send-access-password", templateUrl: "send-access-password.component.html", imports: [SharedModule], - standalone: true, }) export class SendAccessPasswordComponent implements OnInit, OnDestroy { private destroy$ = new Subject<void>(); diff --git a/apps/web/src/app/tools/send/send-access/send-access-text.component.ts b/apps/web/src/app/tools/send/send-access/send-access-text.component.ts index 6f9bc798d4b..2b5405a3f27 100644 --- a/apps/web/src/app/tools/send/send-access/send-access-text.component.ts +++ b/apps/web/src/app/tools/send/send-access/send-access-text.component.ts @@ -14,7 +14,6 @@ import { SharedModule } from "../../../shared"; selector: "app-send-access-text", templateUrl: "send-access-text.component.html", imports: [SharedModule], - standalone: true, }) export class SendAccessTextComponent { private _send: SendAccessView = null; diff --git a/apps/web/src/app/tools/send/send.component.ts b/apps/web/src/app/tools/send/send.component.ts index c6057c654f5..3d42b3182f8 100644 --- a/apps/web/src/app/tools/send/send.component.ts +++ b/apps/web/src/app/tools/send/send.component.ts @@ -41,7 +41,6 @@ const BroadcasterSubscriptionId = "SendComponent"; @Component({ selector: "app-send", - standalone: true, imports: [SharedModule, SearchModule, NoItemsModule, HeaderModule, NewSendDropdownComponent], templateUrl: "send.component.html", providers: [DefaultSendFormConfigService], diff --git a/apps/web/src/app/tools/vault-export/export-web.component.ts b/apps/web/src/app/tools/vault-export/export-web.component.ts index f2612656cee..bf29e83b893 100644 --- a/apps/web/src/app/tools/vault-export/export-web.component.ts +++ b/apps/web/src/app/tools/vault-export/export-web.component.ts @@ -8,7 +8,6 @@ import { SharedModule } from "../../shared"; @Component({ templateUrl: "export-web.component.html", - standalone: true, imports: [SharedModule, ExportComponent, HeaderModule], }) export class ExportWebComponent { diff --git a/apps/web/src/app/tools/vault-export/org-vault-export.component.ts b/apps/web/src/app/tools/vault-export/org-vault-export.component.ts index d84d2b26a90..94cc9bf18f7 100644 --- a/apps/web/src/app/tools/vault-export/org-vault-export.component.ts +++ b/apps/web/src/app/tools/vault-export/org-vault-export.component.ts @@ -9,7 +9,6 @@ import { LooseComponentsModule, SharedModule } from "../../shared"; @Component({ templateUrl: "org-vault-export.component.html", - standalone: true, imports: [SharedModule, ExportComponent, LooseComponentsModule], }) export class OrganizationVaultExportComponent implements OnInit { diff --git a/libs/angular/src/tools/password-strength/password-strength-v2.component.ts b/libs/angular/src/tools/password-strength/password-strength-v2.component.ts index 8d9fc458384..c8a3b071746 100644 --- a/libs/angular/src/tools/password-strength/password-strength-v2.component.ts +++ b/libs/angular/src/tools/password-strength/password-strength-v2.component.ts @@ -20,7 +20,6 @@ type BackgroundTypes = "danger" | "primary" | "success" | "warning"; @Component({ selector: "tools-password-strength", templateUrl: "password-strength-v2.component.html", - standalone: true, imports: [CommonModule, JslibModule, ProgressModule], }) export class PasswordStrengthV2Component implements OnChanges { diff --git a/libs/importer/src/components/dialog/file-password-prompt.component.ts b/libs/importer/src/components/dialog/file-password-prompt.component.ts index d67c60d6b6b..9ad62b7e8f5 100644 --- a/libs/importer/src/components/dialog/file-password-prompt.component.ts +++ b/libs/importer/src/components/dialog/file-password-prompt.component.ts @@ -14,7 +14,6 @@ import { @Component({ templateUrl: "file-password-prompt.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/importer/src/components/dialog/import-error-dialog.component.ts b/libs/importer/src/components/dialog/import-error-dialog.component.ts index 9e09afa7cf1..cb998c2dfe9 100644 --- a/libs/importer/src/components/dialog/import-error-dialog.component.ts +++ b/libs/importer/src/components/dialog/import-error-dialog.component.ts @@ -18,7 +18,6 @@ export interface ErrorListItem { @Component({ templateUrl: "./import-error-dialog.component.html", - standalone: true, imports: [CommonModule, JslibModule, DialogModule, TableModule, ButtonModule], }) export class ImportErrorDialogComponent implements OnInit { diff --git a/libs/importer/src/components/dialog/import-success-dialog.component.ts b/libs/importer/src/components/dialog/import-success-dialog.component.ts index bafd3c26412..ff9a5d7b014 100644 --- a/libs/importer/src/components/dialog/import-success-dialog.component.ts +++ b/libs/importer/src/components/dialog/import-success-dialog.component.ts @@ -22,7 +22,6 @@ export interface ResultList { @Component({ templateUrl: "./import-success-dialog.component.html", - standalone: true, imports: [CommonModule, JslibModule, DialogModule, TableModule, ButtonModule], }) export class ImportSuccessDialogComponent implements OnInit { diff --git a/libs/importer/src/components/dialog/sshkey-password-prompt.component.ts b/libs/importer/src/components/dialog/sshkey-password-prompt.component.ts index 540d576d156..8c199ee5577 100644 --- a/libs/importer/src/components/dialog/sshkey-password-prompt.component.ts +++ b/libs/importer/src/components/dialog/sshkey-password-prompt.component.ts @@ -14,7 +14,6 @@ import { @Component({ templateUrl: "sshkey-password-prompt.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/importer/src/components/import.component.ts b/libs/importer/src/components/import.component.ts index 7b8f49b796b..28137906147 100644 --- a/libs/importer/src/components/import.component.ts +++ b/libs/importer/src/components/import.component.ts @@ -106,7 +106,6 @@ const safeProviders: SafeProvider[] = [ @Component({ selector: "tools-import", templateUrl: "import.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/importer/src/components/lastpass/dialog/lastpass-multifactor-prompt.component.ts b/libs/importer/src/components/lastpass/dialog/lastpass-multifactor-prompt.component.ts index 662a1291547..f497a3bf32c 100644 --- a/libs/importer/src/components/lastpass/dialog/lastpass-multifactor-prompt.component.ts +++ b/libs/importer/src/components/lastpass/dialog/lastpass-multifactor-prompt.component.ts @@ -23,7 +23,6 @@ type LastPassMultifactorPromptData = { @Component({ templateUrl: "lastpass-multifactor-prompt.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/importer/src/components/lastpass/dialog/lastpass-password-prompt.component.ts b/libs/importer/src/components/lastpass/dialog/lastpass-password-prompt.component.ts index e0f0d5b4cfa..861f184f94d 100644 --- a/libs/importer/src/components/lastpass/dialog/lastpass-password-prompt.component.ts +++ b/libs/importer/src/components/lastpass/dialog/lastpass-password-prompt.component.ts @@ -17,7 +17,6 @@ import { @Component({ templateUrl: "lastpass-password-prompt.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/importer/src/components/lastpass/import-lastpass.component.ts b/libs/importer/src/components/lastpass/import-lastpass.component.ts index a08349609d9..7fbf7dd8a7a 100644 --- a/libs/importer/src/components/lastpass/import-lastpass.component.ts +++ b/libs/importer/src/components/lastpass/import-lastpass.component.ts @@ -28,7 +28,6 @@ import { LastPassDirectImportService } from "./lastpass-direct-import.service"; @Component({ selector: "import-lastpass", templateUrl: "import-lastpass.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts index cb16c759ba2..2b03234c5e2 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts +++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export-scope-callout.component.ts @@ -16,7 +16,6 @@ import { CalloutModule } from "@bitwarden/components"; @Component({ selector: "tools-export-scope-callout", templateUrl: "export-scope-callout.component.html", - standalone: true, imports: [CommonModule, JslibModule, CalloutModule], }) export class ExportScopeCalloutComponent { diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts index 956cc611c2e..7773c6a4d4a 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts +++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts @@ -67,7 +67,6 @@ import { ExportScopeCalloutComponent } from "./export-scope-callout.component"; @Component({ selector: "tools-export", templateUrl: "export.component.html", - standalone: true, imports: [ CommonModule, ReactiveFormsModule, diff --git a/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts b/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts index 7559dee130c..9ec0e636f9a 100644 --- a/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts +++ b/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts @@ -29,7 +29,6 @@ import { EmptyCredentialHistoryComponent } from "./empty-credential-history.comp @Component({ templateUrl: "credential-generator-history-dialog.component.html", - standalone: true, imports: [ ButtonModule, CommonModule, diff --git a/libs/tools/generator/components/src/credential-generator-history.component.ts b/libs/tools/generator/components/src/credential-generator-history.component.ts index 76dfbaea867..3965b2be83e 100644 --- a/libs/tools/generator/components/src/credential-generator-history.component.ts +++ b/libs/tools/generator/components/src/credential-generator-history.component.ts @@ -27,7 +27,6 @@ import { GeneratorModule } from "./generator.module"; import { translate } from "./util"; @Component({ - standalone: true, selector: "bit-credential-generator-history", templateUrl: "credential-generator-history.component.html", imports: [ diff --git a/libs/tools/generator/components/src/empty-credential-history.component.ts b/libs/tools/generator/components/src/empty-credential-history.component.ts index 1e23adf0bb1..29c9fc277fc 100644 --- a/libs/tools/generator/components/src/empty-credential-history.component.ts +++ b/libs/tools/generator/components/src/empty-credential-history.component.ts @@ -6,7 +6,6 @@ import { IconModule, TypographyModule } from "@bitwarden/components"; import { NoCredentialsIcon } from "./icons/no-credentials.icon"; @Component({ - standalone: true, selector: "bit-empty-credential-history", templateUrl: "empty-credential-history.component.html", imports: [JslibModule, IconModule, TypographyModule], diff --git a/libs/tools/generator/components/src/nudge-generator-spotlight.component.ts b/libs/tools/generator/components/src/nudge-generator-spotlight.component.ts index a0008bac782..6807a987a85 100644 --- a/libs/tools/generator/components/src/nudge-generator-spotlight.component.ts +++ b/libs/tools/generator/components/src/nudge-generator-spotlight.component.ts @@ -11,7 +11,6 @@ import { TypographyModule } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; @Component({ - standalone: true, selector: "nudge-generator-spotlight", templateUrl: "nudge-generator-spotlight.component.html", imports: [I18nPipe, SpotlightComponent, AsyncPipe, CommonModule, TypographyModule], diff --git a/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts b/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts index 0bb753d3f37..4bcf11bf94f 100644 --- a/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts +++ b/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts @@ -54,7 +54,6 @@ export enum SendItemDialogResult { */ @Component({ templateUrl: "send-add-edit-dialog.component.html", - standalone: true, imports: [ CommonModule, SearchModule, diff --git a/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.ts b/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.ts index 6563789f1c7..ba5176e5db5 100644 --- a/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.ts +++ b/libs/tools/send/send-ui/src/new-send-dropdown/new-send-dropdown.component.ts @@ -12,7 +12,6 @@ import { BadgeModule, ButtonModule, ButtonType, MenuModule } from "@bitwarden/co @Component({ selector: "tools-new-send-dropdown", templateUrl: "new-send-dropdown.component.html", - standalone: true, imports: [JslibModule, CommonModule, ButtonModule, RouterLink, MenuModule, BadgeModule], }) export class NewSendDropdownComponent implements OnInit { diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts index 30775aa8a83..b2ab149f2f2 100644 --- a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.ts @@ -36,7 +36,6 @@ import { SendFormContainer } from "../../send-form-container"; @Component({ selector: "tools-send-options", templateUrl: "./send-options.component.html", - standalone: true, imports: [ AsyncActionsModule, ButtonModule, diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts index 9ca9aefb4ac..e1fbf5dbc50 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts @@ -50,7 +50,6 @@ export interface DatePresetSelectOption { @Component({ selector: "tools-send-details", templateUrl: "./send-details.component.html", - standalone: true, imports: [ SectionComponent, SectionHeaderComponent, diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-file-details.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-details/send-file-details.component.ts index a8f878aab23..9d967e15ba8 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-file-details.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-file-details.component.ts @@ -22,7 +22,6 @@ import { SendFormContainer } from "../../send-form-container"; @Component({ selector: "tools-send-file-details", templateUrl: "./send-file-details.component.html", - standalone: true, imports: [ ButtonModule, CommonModule, diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.ts index e896f4c2bc2..ac8ee0c8d71 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-text-details.component.ts @@ -15,7 +15,6 @@ import { SendFormContainer } from "../../send-form-container"; @Component({ selector: "tools-send-text-details", templateUrl: "./send-text-details.component.html", - standalone: true, imports: [ CheckboxModule, CommonModule, diff --git a/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts index 13c00a6bb78..b8593c735b7 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-form.component.ts @@ -41,7 +41,6 @@ import { SendDetailsComponent } from "./send-details/send-details.component"; @Component({ selector: "tools-send-form", templateUrl: "./send-form.component.html", - standalone: true, providers: [ { provide: SendFormContainer, diff --git a/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.ts b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.ts index d42eab382e9..b7c60145bbf 100644 --- a/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.ts +++ b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.ts @@ -11,7 +11,6 @@ import { ChipSelectComponent } from "@bitwarden/components"; import { SendListFiltersService } from "../services/send-list-filters.service"; @Component({ - standalone: true, selector: "app-send-list-filters", templateUrl: "./send-list-filters.component.html", imports: [CommonModule, JslibModule, ChipSelectComponent, ReactiveFormsModule], diff --git a/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.ts b/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.ts index ab73e71c4ab..f67880eb73f 100644 --- a/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.ts +++ b/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.ts @@ -40,7 +40,6 @@ import { ], selector: "app-send-list-items-container", templateUrl: "send-list-items-container.component.html", - standalone: true, }) export class SendListItemsContainerComponent { sendType = SendType; diff --git a/libs/tools/send/send-ui/src/send-search/send-search.component.ts b/libs/tools/send/send-ui/src/send-search/send-search.component.ts index 8142ce58f64..90b31a206fc 100644 --- a/libs/tools/send/send-ui/src/send-search/send-search.component.ts +++ b/libs/tools/send/send-ui/src/send-search/send-search.component.ts @@ -13,7 +13,6 @@ const SearchTextDebounceInterval = 200; @Component({ imports: [CommonModule, SearchModule, JslibModule, FormsModule], - standalone: true, selector: "tools-send-search", templateUrl: "send-search.component.html", }) From 618ab229e9c771067b36d45f59a21e94d04b5753 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Tue, 3 Jun 2025 08:50:50 -0400 Subject: [PATCH 224/245] Remove pm-18794-provider-payment-method (#14865) --- .../subscription/provider-subscription.component.html | 2 +- .../subscription/provider-subscription.component.ts | 7 ------- libs/common/src/enums/feature-flag.enum.ts | 2 -- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html index f2f72fa5bb4..7f2b205fc22 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html @@ -71,7 +71,7 @@ <p bitTypography="body1">{{ "creditAppliedDesc" | i18n }}</p> </bit-section> <!-- Payment Method --> - <bit-section *ngIf="allowProviderPaymentMethod$ | async"> + <bit-section> <h2 bitTypography="h2">{{ "paymentMethod" | i18n }}</h2> <p *ngIf="!subscription.paymentSource" bitTypography="body1"> {{ "noPaymentMethod" | i18n }} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts index 74368ef7839..cff2d8e63fe 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts @@ -13,8 +13,6 @@ import { ProviderPlanResponse, ProviderSubscriptionResponse, } from "@bitwarden/common/billing/models/response/provider-subscription-response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service"; @@ -39,10 +37,6 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy { protected readonly TaxInformation = TaxInformation; - protected readonly allowProviderPaymentMethod$ = this.configService.getFeatureFlag$( - FeatureFlag.PM18794_ProviderPaymentMethod, - ); - constructor( private billingApiService: BillingApiServiceAbstraction, private i18nService: I18nService, @@ -50,7 +44,6 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy { private billingNotificationService: BillingNotificationService, private dialogService: DialogService, private toastService: ToastService, - private configService: ConfigService, ) {} async ngOnInit() { diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index dbddc426e73..91d24ef3e9d 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -29,7 +29,6 @@ export enum FeatureFlag { /* Billing */ TrialPaymentOptional = "PM-8163-trial-payment", PM12276_BreadcrumbEventLogs = "pm-12276-breadcrumbing-for-business-features", - PM18794_ProviderPaymentMethod = "pm-18794-provider-payment-method", PM17772_AdminInitiatedSponsorships = "pm-17772-admin-initiated-sponsorships", PM19956_RequireProviderPaymentMethodDuringSetup = "pm-19956-require-provider-payment-method-during-setup", UseOrganizationWarningsService = "use-organization-warnings-service", @@ -113,7 +112,6 @@ export const DefaultFeatureFlagValue = { /* Billing */ [FeatureFlag.TrialPaymentOptional]: FALSE, [FeatureFlag.PM12276_BreadcrumbEventLogs]: FALSE, - [FeatureFlag.PM18794_ProviderPaymentMethod]: FALSE, [FeatureFlag.PM17772_AdminInitiatedSponsorships]: FALSE, [FeatureFlag.PM19956_RequireProviderPaymentMethodDuringSetup]: FALSE, [FeatureFlag.UseOrganizationWarningsService]: FALSE, From deb9ba6e31c364a120d24e565076a91b8fe1f2a6 Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Tue, 3 Jun 2025 15:42:08 +0200 Subject: [PATCH 225/245] Fix ng select and product switcher (#15046) * Fix ng select and product switcher * Fix story * Fix tests --- .../navigation-switcher.stories.ts | 11 ++--------- .../product-switcher/product-switcher.module.ts | 5 ++--- .../shared/product-switcher.service.spec.ts | 6 +++--- .../shared/product-switcher.service.ts | 8 ++++---- apps/web/webpack.config.js | 2 +- .../secrets-manager/overview/overview.component.ts | 2 -- 6 files changed, 12 insertions(+), 22 deletions(-) diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index 0ecec9d8944..f0660f7d655 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -3,7 +3,6 @@ import { RouterModule } from "@angular/router"; import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; @@ -18,6 +17,7 @@ import { LayoutComponent, NavigationModule } from "@bitwarden/components"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports import { I18nMockService } from "@bitwarden/components/src/utils/i18n-mock.service"; +import { I18nPipe } from "@bitwarden/ui-common"; import { ProductSwitcherService } from "../shared/product-switcher.service"; @@ -109,9 +109,8 @@ export default { MockProviderService, StoryLayoutComponent, StoryContentComponent, - I18nPipe, ], - imports: [NavigationModule, RouterModule, LayoutComponent], + imports: [NavigationModule, RouterModule, LayoutComponent, I18nPipe], providers: [ { provide: OrganizationService, useClass: MockOrganizationService }, { provide: AccountService, useClass: MockAccountService }, @@ -119,12 +118,6 @@ export default { { provide: SyncService, useClass: MockSyncService }, { provide: PlatformUtilsService, useClass: MockPlatformUtilsService }, ProductSwitcherService, - { - provide: I18nPipe, - useFactory: () => ({ - transform: (key: string) => translations[key], - }), - }, { provide: I18nService, useFactory: () => { diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.module.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.module.ts index d43bca1c0b9..b78b1ce6b96 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.module.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.module.ts @@ -2,8 +2,8 @@ import { A11yModule } from "@angular/cdk/a11y"; import { NgModule } from "@angular/core"; import { RouterModule } from "@angular/router"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { NavigationModule } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; import { SharedModule } from "../../shared"; @@ -12,13 +12,12 @@ import { ProductSwitcherContentComponent } from "./product-switcher-content.comp import { ProductSwitcherComponent } from "./product-switcher.component"; @NgModule({ - imports: [SharedModule, A11yModule, RouterModule, NavigationModule], + imports: [SharedModule, A11yModule, RouterModule, NavigationModule, I18nPipe], declarations: [ ProductSwitcherComponent, ProductSwitcherContentComponent, NavigationProductSwitcherComponent, ], exports: [ProductSwitcherComponent, NavigationProductSwitcherComponent], - providers: [I18nPipe], }) export class ProductSwitcherModule {} diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts index 4abd85d7991..f72557ac57f 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts @@ -5,13 +5,13 @@ import { ActivatedRoute, Router, convertToParamMap } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; import { Observable, firstValueFrom, of } from "rxjs"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SyncService } from "@bitwarden/common/platform/sync"; @@ -70,9 +70,9 @@ describe("ProductSwitcherService", () => { }, }, { - provide: I18nPipe, + provide: I18nService, useValue: { - transform: (key: string) => key, + t: (id: string, p1?: string | number, p2?: string | number, p3?: string | number) => id, }, }, { diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts index 53ec3b0840f..2d296ac7d62 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts @@ -14,7 +14,6 @@ import { switchMap, } from "rxjs"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { canAccessOrgAdmin, OrganizationService, @@ -25,6 +24,7 @@ import { PolicyType, ProviderType } from "@bitwarden/common/admin-console/enums" import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; @@ -103,11 +103,11 @@ export class ProductSwitcherService { private providerService: ProviderService, private route: ActivatedRoute, private router: Router, - private i18n: I18nPipe, private syncService: SyncService, private accountService: AccountService, private platformUtilsService: PlatformUtilsService, private policyService: PolicyService, + private i18nService: I18nService, ) { this.pollUntilSynced(); } @@ -197,7 +197,7 @@ export class ProductSwitcherService { }, isActive: this.router.url.includes("/sm/"), otherProductOverrides: { - supportingText: this.i18n.transform("secureYourInfrastructure"), + supportingText: this.i18nService.t("secureYourInfrastructure"), }, }, ac: { @@ -222,7 +222,7 @@ export class ProductSwitcherService { marketingRoute: orgsMarketingRoute, otherProductOverrides: { name: "Share your passwords", - supportingText: this.i18n.transform("protectYourFamilyOrBusiness"), + supportingText: this.i18nService.t("protectYourFamilyOrBusiness"), }, }, } satisfies Record<string, ProductSwitcherItem>; diff --git a/apps/web/webpack.config.js b/apps/web/webpack.config.js index d8b9fd3dbee..d564baaa60f 100644 --- a/apps/web/webpack.config.js +++ b/apps/web/webpack.config.js @@ -259,7 +259,7 @@ const devServer = 'sha256-JVRXyYPueLWdwGwY9m/7u4QlZ1xeQdqUj2t8OVIzZE4=' 'sha256-or0p3LaHetJ4FRq+flVORVFFNsOjQGWrDvX8Jf7ACWg=' 'sha256-jvLh2uL2/Pq/gpvNJMaEL4C+TNhBeGadLIUyPcVRZvY=' - 'sha256-VZTcMoTEw3nbAHejvqlyyRm1Mdx+DVNgyKANjpWw0qg=' + 'sha256-EnIJNDxVnh0++RytXJOkU0sqtLDFt1nYUDOfeJ5SKxg=' ;img-src 'self' data: diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts index a96f9a08919..1fd0afd3458 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts @@ -20,7 +20,6 @@ import { from, } from "rxjs"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { getOrganizationById, @@ -100,7 +99,6 @@ export class OverviewComponent implements OnInit, OnDestroy { protected loading = true; protected organizationEnabled = false; protected organization: Organization; - protected i18n: I18nPipe; protected onboardingTasks$: Observable<SMOnboardingTasks>; protected view$: Observable<{ From 24ae013f716d0060fae224dd3e09f665a1cc4d82 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 3 Jun 2025 09:04:29 -0500 Subject: [PATCH 226/245] [PM-22269] Generator/Send Nudge Updates (#15049) * remove margin from the bottom of paragraph tag in generator spotlight * update aria-label text to match translation key * Remove `SendNudgeStatus` nudge type * update web send page to use new title and description * update old no sends title and description * hide internal contents on generator nudge * remove NudgeService from send-v2 test --- apps/browser/src/_locales/en/messages.json | 8 ----- .../popup/send-v2/send-v2.component.html | 33 ++++++------------- .../popup/send-v2/send-v2.component.spec.ts | 2 -- .../tools/popup/send-v2/send-v2.component.ts | 10 +----- .../src/app/tools/send/send.component.html | 4 +-- apps/web/src/locales/en/messages.json | 8 ----- .../src/vault/services/nudges.service.ts | 1 - .../nudge-generator-spotlight.component.html | 10 +++--- 8 files changed, 19 insertions(+), 57 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 68f303dd538..032d8c89d49 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3614,14 +3614,6 @@ "trustUser": { "message": "Trust user" }, - "sendsNoItemsTitle": { - "message": "No active Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendsTitleNoItems": { "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.html b/apps/browser/src/tools/popup/send-v2/send-v2.component.html index d271f67fa3b..082112a86ab 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.html +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.html @@ -20,29 +20,16 @@ *ngIf="listState === sendState.Empty" class="tw-flex tw-flex-col tw-h-full tw-justify-center" > - <ng-container *ngIf="!(showSendSpotlight$ | async)"> - <bit-no-items [icon]="noItemIcon" class="tw-text-main"> - <ng-container slot="title">{{ "sendsNoItemsTitle" | i18n }}</ng-container> - <ng-container slot="description">{{ "sendsNoItemsMessage" | i18n }}</ng-container> - <tools-new-send-dropdown - [hideIcon]="true" - *ngIf="!sendsDisabled" - slot="button" - ></tools-new-send-dropdown> - </bit-no-items> - </ng-container> - <ng-container *ngIf="showSendSpotlight$ | async"> - <bit-no-items [icon]="noItemIcon" class="tw-text-main"> - <ng-container slot="title">{{ "sendsTitleNoItems" | i18n }}</ng-container> - <ng-container slot="description">{{ "sendsBodyNoItems" | i18n }}</ng-container> - <tools-new-send-dropdown - [hideIcon]="true" - *ngIf="!sendsDisabled" - slot="button" - [buttonType]="'secondary'" - ></tools-new-send-dropdown> - </bit-no-items> - </ng-container> + <bit-no-items [icon]="noItemIcon" class="tw-text-main"> + <ng-container slot="title">{{ "sendsTitleNoItems" | i18n }}</ng-container> + <ng-container slot="description">{{ "sendsBodyNoItems" | i18n }}</ng-container> + <tools-new-send-dropdown + [hideIcon]="true" + *ngIf="!sendsDisabled" + slot="button" + [buttonType]="'secondary'" + ></tools-new-send-dropdown> + </bit-no-items> </div> <ng-container *ngIf="listState !== sendState.Empty"> diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts index c1f8e9fb263..6fc4793f5c0 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts @@ -6,7 +6,6 @@ import { MockProxy, mock } from "jest-mock-extended"; import { of, BehaviorSubject } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { NudgesService } from "@bitwarden/angular/vault"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -122,7 +121,6 @@ describe("SendV2Component", () => { { provide: SendListFiltersService, useValue: sendListFiltersService }, { provide: PopupRouterCacheService, useValue: mock<PopupRouterCacheService>() }, { provide: PolicyService, useValue: policyService }, - { provide: NudgesService, useValue: mock<NudgesService>() }, ], }).compileComponents(); diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts index e2b5551cc7d..2fca3e41f88 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts @@ -1,10 +1,9 @@ import { CommonModule } from "@angular/common"; import { Component, OnDestroy } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { combineLatest, Observable, switchMap } from "rxjs"; +import { combineLatest, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -61,12 +60,6 @@ export class SendV2Component implements OnDestroy { protected title: string = "allSends"; protected noItemIcon = NoSendsIcon; protected noResultsIcon = Icons.NoResults; - private activeUserId$ = this.accountService.activeAccount$.pipe(getUserId); - protected showSendSpotlight$: Observable<boolean> = this.activeUserId$.pipe( - switchMap((userId) => - this.nudgesService.showNudgeSpotlight$(NudgeType.SendNudgeStatus, userId), - ), - ); protected sendsDisabled = false; @@ -75,7 +68,6 @@ export class SendV2Component implements OnDestroy { protected sendListFiltersService: SendListFiltersService, private policyService: PolicyService, private accountService: AccountService, - private nudgesService: NudgesService, ) { combineLatest([ this.sendItemsService.emptyList$, diff --git a/apps/web/src/app/tools/send/send.component.html b/apps/web/src/app/tools/send/send.component.html index 1f220f4551e..042046b85ff 100644 --- a/apps/web/src/app/tools/send/send.component.html +++ b/apps/web/src/app/tools/send/send.component.html @@ -194,8 +194,8 @@ </ng-container> <ng-container *ngIf="loaded"> <bit-no-items [icon]="noItemIcon" class="tw-text-main"> - <ng-container slot="title">{{ "sendsNoItemsTitle" | i18n }}</ng-container> - <ng-container slot="description">{{ "sendsNoItemsMessage" | i18n }}</ng-container> + <ng-container slot="title">{{ "sendsTitleNoItems" | i18n }}</ng-container> + <ng-container slot="description">{{ "sendsBodyNoItems" | i18n }}</ng-container> <tools-new-send-dropdown [hideIcon]="true" *ngIf="!disableSend" diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index a217b38e650..7735286856b 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -8608,14 +8608,6 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "inviteUsers": { "message": "Invite Users" }, diff --git a/libs/angular/src/vault/services/nudges.service.ts b/libs/angular/src/vault/services/nudges.service.ts index 006f568f33e..9ba46a3bb6d 100644 --- a/libs/angular/src/vault/services/nudges.service.ts +++ b/libs/angular/src/vault/services/nudges.service.ts @@ -41,7 +41,6 @@ export enum NudgeType { NewNoteItemStatus = "new-note-item-status", NewSshItemStatus = "new-ssh-item-status", GeneratorNudgeStatus = "generator-nudge-status", - SendNudgeStatus = "send-nudge-status", } export const NUDGE_DISMISSED_DISK_KEY = new UserKeyDefinition< diff --git a/libs/tools/generator/components/src/nudge-generator-spotlight.component.html b/libs/tools/generator/components/src/nudge-generator-spotlight.component.html index 9c65a1cea96..b06db8b83e1 100644 --- a/libs/tools/generator/components/src/nudge-generator-spotlight.component.html +++ b/libs/tools/generator/components/src/nudge-generator-spotlight.component.html @@ -4,12 +4,14 @@ (onDismiss)="dismissGeneratorSpotlight(NudgeType.GeneratorNudgeStatus)" > <p - class="tw-text-main" + class="tw-text-main tw-mb-0" bitTypography="body2" - [attr.aria-label]="'generatorNudgeIconAria' | i18n" + [attr.aria-label]="'generatorNudgeBodyAria' | i18n" > - {{ "generatorNudgeBodyOne" | i18n }} <i class="bwi bwi-generate"></i> - {{ "generatorNudgeBodyTwo" | i18n }} + <span aria-hidden="true"> + {{ "generatorNudgeBodyOne" | i18n }} <i class="bwi bwi-generate"></i> + {{ "generatorNudgeBodyTwo" | i18n }} + </span> </p> </bit-spotlight> </div> From 8a29df64d9c762802bf41e95c4c0267dc746dd51 Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Tue, 3 Jun 2025 11:08:29 -0400 Subject: [PATCH 227/245] [PM-20398] Add Notifications logging (#13640) * Add Logging to know which notification transport is being used * Remove debug log --- .../internal/default-notifications.service.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libs/common/src/platform/notifications/internal/default-notifications.service.ts b/libs/common/src/platform/notifications/internal/default-notifications.service.ts index 4cbc8227364..ff22173a26e 100644 --- a/libs/common/src/platform/notifications/internal/default-notifications.service.ts +++ b/libs/common/src/platform/notifications/internal/default-notifications.service.ts @@ -108,14 +108,19 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract return this.webPushConnectionService.supportStatus$(userId); }), supportSwitch({ - supported: (service) => - service.notifications$.pipe( + supported: (service) => { + this.logService.info("Using WebPush for notifications"); + return service.notifications$.pipe( catchError((err: unknown) => { this.logService.warning("Issue with web push, falling back to SignalR", err); return this.connectSignalR$(userId, notificationsUrl); }), - ), - notSupported: () => this.connectSignalR$(userId, notificationsUrl), + ); + }, + notSupported: () => { + this.logService.info("Using SignalR for notifications"); + return this.connectSignalR$(userId, notificationsUrl); + }, }), ); } From 2e66addd6a171db8df7827639a2135cca785b49b Mon Sep 17 00:00:00 2001 From: SmithThe4th <gsmith@bitwarden.com> Date: Tue, 3 Jun 2025 12:20:29 -0400 Subject: [PATCH 228/245] [PM-19632] Remove security task flag - step 1 (#14904) * Step 1- remove business logic * removed dependency * removed leftover flags --- .../browser/src/background/main.background.ts | 5 +--- .../vault-v2/vault-v2.component.html | 4 +-- .../src/services/jslib-services.module.ts | 1 - libs/common/src/enums/feature-flag.enum.ts | 2 -- .../services/default-task.service.spec.ts | 25 ------------------- .../tasks/services/default-task.service.ts | 12 ++------- 6 files changed, 4 insertions(+), 45 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 5225ebc0fb1..1b4afabeb66 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1203,7 +1203,6 @@ export default class MainBackground { this.stateProvider, this.apiService, this.organizationService, - this.configService, this.authService, this.notificationsService, messageListener, @@ -1423,9 +1422,7 @@ export default class MainBackground { this.backgroundSyncService.init(); this.notificationsService.startListening(); - if (await this.configService.getFeatureFlag(FeatureFlag.SecurityTasks)) { - this.taskService.listenForTaskNotifications(); - } + this.taskService.listenForTaskNotifications(); if (await this.configService.getFeatureFlag(FeatureFlag.EndUserNotifications)) { this.endUserNotificationService.listenForEndUserNotifications(); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index 42e772be062..da7b1393590 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -53,9 +53,7 @@ </ul> </bit-spotlight> </div> - <vault-at-risk-password-callout - *appIfFeature="FeatureFlag.SecurityTasks" - ></vault-at-risk-password-callout> + <vault-at-risk-password-callout></vault-at-risk-password-callout> <app-vault-header-v2></app-vault-header-v2> </ng-container> </ng-container> diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 08bcaa2165c..1f5adb6260e 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1511,7 +1511,6 @@ const safeProviders: SafeProvider[] = [ StateProvider, ApiServiceAbstraction, OrganizationServiceAbstraction, - ConfigService, AuthServiceAbstraction, NotificationsService, MessageListener, diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 91d24ef3e9d..b78f5a1deec 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -51,7 +51,6 @@ export enum FeatureFlag { /* Vault */ PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge", PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form", - SecurityTasks = "security-tasks", PM19941MigrateCipherDomainToSdk = "pm-19941-migrate-cipher-domain-to-sdk", CipherKeyEncryption = "cipher-key-encryption", PM18520_UpdateDesktopCipherForm = "pm-18520-desktop-cipher-forms", @@ -98,7 +97,6 @@ export const DefaultFeatureFlagValue = { /* Vault */ [FeatureFlag.PM8851_BrowserOnboardingNudge]: FALSE, [FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE, - [FeatureFlag.SecurityTasks]: FALSE, [FeatureFlag.CipherKeyEncryption]: FALSE, [FeatureFlag.PM18520_UpdateDesktopCipherForm]: FALSE, [FeatureFlag.EndUserNotifications]: FALSE, diff --git a/libs/common/src/vault/tasks/services/default-task.service.spec.ts b/libs/common/src/vault/tasks/services/default-task.service.spec.ts index cb22d1296ba..d90889cf113 100644 --- a/libs/common/src/vault/tasks/services/default-task.service.spec.ts +++ b/libs/common/src/vault/tasks/services/default-task.service.spec.ts @@ -7,7 +7,6 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { NotificationType } from "@bitwarden/common/enums"; import { NotificationResponse } from "@bitwarden/common/models/response/notification.response"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { Message, MessageListener } from "@bitwarden/common/platform/messaging"; import { NotificationsService } from "@bitwarden/common/platform/notifications"; import { SecurityTaskId, UserId } from "@bitwarden/common/types/guid"; @@ -25,7 +24,6 @@ describe("Default task service", () => { const userId = "user-id" as UserId; const mockApiSend = jest.fn(); const mockGetAllOrgs$ = jest.fn(); - const mockGetFeatureFlag$ = jest.fn(); const mockAuthStatuses$ = new BehaviorSubject<Record<UserId, AuthenticationStatus>>({}); const mockNotifications$ = new Subject<readonly [NotificationResponse, UserId]>(); const mockMessages$ = new Subject<Message<Record<string, unknown>>>(); @@ -34,14 +32,12 @@ describe("Default task service", () => { beforeEach(async () => { mockApiSend.mockClear(); mockGetAllOrgs$.mockClear(); - mockGetFeatureFlag$.mockClear(); fakeStateProvider = new FakeStateProvider(mockAccountServiceWith(userId)); service = new DefaultTaskService( fakeStateProvider, { send: mockApiSend } as unknown as ApiService, { organizations$: mockGetAllOrgs$ } as unknown as OrganizationService, - { getFeatureFlag$: mockGetFeatureFlag$ } as unknown as ConfigService, { authStatuses$: mockAuthStatuses$.asObservable() } as unknown as AuthService, { notifications$: mockNotifications$.asObservable() } as unknown as NotificationsService, { allMessages$: mockMessages$.asObservable() } as unknown as MessageListener, @@ -50,7 +46,6 @@ describe("Default task service", () => { describe("tasksEnabled$", () => { it("should emit true if any organization uses risk insights", async () => { - mockGetFeatureFlag$.mockReturnValue(new BehaviorSubject(true)); mockGetAllOrgs$.mockReturnValue( new BehaviorSubject([ { @@ -70,7 +65,6 @@ describe("Default task service", () => { }); it("should emit false if no organization uses risk insights", async () => { - mockGetFeatureFlag$.mockReturnValue(new BehaviorSubject(true)); mockGetAllOrgs$.mockReturnValue( new BehaviorSubject([ { @@ -88,28 +82,10 @@ describe("Default task service", () => { expect(result).toBe(false); }); - - it("should emit false if the feature flag is off", async () => { - mockGetFeatureFlag$.mockReturnValue(new BehaviorSubject(false)); - mockGetAllOrgs$.mockReturnValue( - new BehaviorSubject([ - { - useRiskInsights: true, - }, - ] as Organization[]), - ); - - const { tasksEnabled$ } = service; - - const result = await firstValueFrom(tasksEnabled$("user-id" as UserId)); - - expect(result).toBe(false); - }); }); describe("tasks$", () => { beforeEach(() => { - mockGetFeatureFlag$.mockReturnValue(new BehaviorSubject(true)); mockGetAllOrgs$.mockReturnValue( new BehaviorSubject([ { @@ -182,7 +158,6 @@ describe("Default task service", () => { describe("pendingTasks$", () => { beforeEach(() => { - mockGetFeatureFlag$.mockReturnValue(new BehaviorSubject(true)); mockGetAllOrgs$.mockReturnValue( new BehaviorSubject([ { diff --git a/libs/common/src/vault/tasks/services/default-task.service.ts b/libs/common/src/vault/tasks/services/default-task.service.ts index a50f736f7fd..5858ba832d5 100644 --- a/libs/common/src/vault/tasks/services/default-task.service.ts +++ b/libs/common/src/vault/tasks/services/default-task.service.ts @@ -15,9 +15,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { NotificationType } from "@bitwarden/common/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { MessageListener } from "@bitwarden/common/platform/messaging"; import { NotificationsService } from "@bitwarden/common/platform/notifications"; import { StateProvider } from "@bitwarden/common/platform/state"; @@ -43,20 +41,14 @@ export class DefaultTaskService implements TaskService { private stateProvider: StateProvider, private apiService: ApiService, private organizationService: OrganizationService, - private configService: ConfigService, private authService: AuthService, private notificationService: NotificationsService, private messageListener: MessageListener, ) {} tasksEnabled$ = perUserCache$((userId) => { - return combineLatest([ - this.organizationService - .organizations$(userId) - .pipe(map((orgs) => orgs.some((o) => o.useRiskInsights))), - this.configService.getFeatureFlag$(FeatureFlag.SecurityTasks), - ]).pipe( - map(([atLeastOneOrgEnabled, flagEnabled]) => atLeastOneOrgEnabled && flagEnabled), + return this.organizationService.organizations$(userId).pipe( + map((orgs) => orgs.some((o) => o.useRiskInsights)), distinctUntilChanged(), ); }); From 5fd7c181de95ebdcea30b8129e106ae7da070bde Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Tue, 3 Jun 2025 19:57:17 +0200 Subject: [PATCH 229/245] Remove standalone true from dirt (#15041) Remove standalone: true from every instance since it's the default as of Angular 19. --- .../app/dirt/access-intelligence/all-applications.component.ts | 1 - .../access-intelligence/app-table-row-scrollable.component.ts | 1 - .../dirt/access-intelligence/critical-applications.component.ts | 1 - .../dirt/access-intelligence/notified-members-table.component.ts | 1 - .../access-intelligence/password-health-members-uri.component.ts | 1 - .../access-intelligence/password-health-members.component.ts | 1 - .../app/dirt/access-intelligence/password-health.component.ts | 1 - .../dirt/access-intelligence/risk-insights-loading.component.ts | 1 - .../src/app/dirt/access-intelligence/risk-insights.component.ts | 1 - .../member-access-report/member-access-report.component.ts | 1 - libs/dirt/card/src/card.component.ts | 1 - 11 files changed, 11 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts index 8225571cb98..b5f5773727f 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts @@ -41,7 +41,6 @@ import { AppTableRowScrollableComponent } from "./app-table-row-scrollable.compo import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"; @Component({ - standalone: true, selector: "tools-all-applications", templateUrl: "./all-applications.component.html", imports: [ diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts index 4d373072733..1b3970d7b04 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts @@ -9,7 +9,6 @@ import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pip @Component({ selector: "app-table-row-scrollable", - standalone: true, imports: [CommonModule, JslibModule, TableModule, SharedModule, PipesModule, MenuModule], templateUrl: "./app-table-row-scrollable.component.html", }) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts index 99ef85d43c7..183869c55fa 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts @@ -39,7 +39,6 @@ import { AppTableRowScrollableComponent } from "./app-table-row-scrollable.compo import { RiskInsightsTabType } from "./risk-insights.component"; @Component({ - standalone: true, selector: "tools-critical-applications", templateUrl: "./critical-applications.component.html", imports: [ diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/notified-members-table.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/notified-members-table.component.ts index d50436061cb..15dc80a1b00 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/notified-members-table.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/notified-members-table.component.ts @@ -5,7 +5,6 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { TableDataSource, TableModule } from "@bitwarden/components"; @Component({ - standalone: true, selector: "tools-notified-members-table", templateUrl: "./notified-members-table.component.html", imports: [CommonModule, JslibModule, TableModule], diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/password-health-members-uri.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/password-health-members-uri.component.ts index 9e377f93ef9..a4e8dd0ded8 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/password-health-members-uri.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/password-health-members-uri.component.ts @@ -29,7 +29,6 @@ import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.mod import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; @Component({ - standalone: true, selector: "tools-password-health-members-uri", templateUrl: "password-health-members-uri.component.html", imports: [ diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/password-health-members.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/password-health-members.component.ts index 114d582c363..8cad1f2f8ce 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/password-health-members.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/password-health-members.component.ts @@ -27,7 +27,6 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; @Component({ - standalone: true, selector: "tools-password-health-members", templateUrl: "password-health-members.component.html", imports: [PipesModule, HeaderModule, SearchModule, FormsModule, SharedModule, TableModule], diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/password-health.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/password-health.component.ts index aa8f6731ecf..16c783c3f4f 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/password-health.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/password-health.component.ts @@ -21,7 +21,6 @@ import { OrganizationBadgeModule } from "@bitwarden/web-vault/app/vault/individu import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; @Component({ - standalone: true, selector: "tools-password-health", templateUrl: "password-health.component.html", imports: [ diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights-loading.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights-loading.component.ts index 1cafa62c608..af61c9a35c8 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights-loading.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights-loading.component.ts @@ -5,7 +5,6 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; @Component({ selector: "tools-risk-insights-loading", - standalone: true, imports: [CommonModule, JslibModule], templateUrl: "./risk-insights-loading.component.html", }) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts index 54c02617f00..11f7f336f61 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts @@ -44,7 +44,6 @@ export enum RiskInsightsTabType { } @Component({ - standalone: true, templateUrl: "./risk-insights.component.html", imports: [ AllApplicationsComponent, diff --git a/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/member-access-report.component.ts b/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/member-access-report.component.ts index da78fd29379..b9cab679560 100644 --- a/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/member-access-report.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/member-access-report.component.ts @@ -41,7 +41,6 @@ import { MemberAccessReportView } from "./view/member-access-report.view"; deps: [MemberAccessReportApiService, I18nService], }), ], - standalone: true, }) export class MemberAccessReportComponent implements OnInit { protected dataSource = new TableDataSource<MemberAccessReportView>(); diff --git a/libs/dirt/card/src/card.component.ts b/libs/dirt/card/src/card.component.ts index 37596f7cf47..f9899125dbd 100644 --- a/libs/dirt/card/src/card.component.ts +++ b/libs/dirt/card/src/card.component.ts @@ -9,7 +9,6 @@ import { TypographyModule } from "@bitwarden/components"; @Component({ selector: "dirt-card", templateUrl: "./card.component.html", - standalone: true, imports: [CommonModule, TypographyModule, JslibModule], host: { class: From ce3ce17010f68fcd6de0e69e4806fcf92687b241 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Tue, 3 Jun 2025 22:12:11 +0200 Subject: [PATCH 230/245] [PM-21147] User key transferred over ipc within desktop app without its prototype (#15047) * user key transferred over ipc within desktop app without its prototype. `UserKey` object was transferred over IPC as regular `Object` type and not recreated as `SymmetricCryptoKey` type, losing its original functions and properties. As a result `inner` method did not exist and user key silently failed during decryption of encrypted client key halves during biometric unlock. * ipc biometrics serializable user key type * use encrypt service directly for decryption * moving electron key service to KM * log error when unlock via biometrics fails with exception in lock component * bring back tech debt comment * lock component logging prefix --- .github/codecov.yml | 1 + .../src/app/services/services.module.ts | 2 +- .../renderer-biometrics.service.spec.ts | 34 ++++ .../biometrics/renderer-biometrics.service.ts | 8 +- .../electron-key.service.spec.ts | 188 ++++++++++++++++++ .../electron-key.service.ts | 19 +- apps/desktop/src/key-management/preload.ts | 3 +- .../src/lock/components/lock.component.ts | 2 + 8 files changed, 247 insertions(+), 10 deletions(-) create mode 100644 apps/desktop/src/key-management/electron-key.service.spec.ts rename apps/desktop/src/{platform/services => key-management}/electron-key.service.ts (88%) diff --git a/.github/codecov.yml b/.github/codecov.yml index d9d59f9de28..ba4c4b48163 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -44,6 +44,7 @@ component_management: - component_id: key-management-keys name: Key Management - Keys paths: + - apps/desktop/src/key-management/electron-key.service.ts - libs/key-management/src/kdf-config.service.ts - libs/key-management/src/key.service.ts - libs/common/src/key-management/master-password/** diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index cfab600505e..06c42c5b0bc 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -114,10 +114,10 @@ import { DesktopAutofillService } from "../../autofill/services/desktop-autofill import { DesktopFido2UserInterfaceService } from "../../autofill/services/desktop-fido2-user-interface.service"; import { DesktopBiometricsService } from "../../key-management/biometrics/desktop.biometrics.service"; import { RendererBiometricsService } from "../../key-management/biometrics/renderer-biometrics.service"; +import { ElectronKeyService } from "../../key-management/electron-key.service"; import { DesktopLockComponentService } from "../../key-management/lock/services/desktop-lock-component.service"; import { flagEnabled } from "../../platform/flags"; import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; -import { ElectronKeyService } from "../../platform/services/electron-key.service"; import { ElectronLogRendererService } from "../../platform/services/electron-log.renderer.service"; import { ELECTRON_SUPPORTS_SECURE_STORAGE, diff --git a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.spec.ts b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.spec.ts index 7a3f00c7c44..2901b02ab6c 100644 --- a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.spec.ts +++ b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.spec.ts @@ -1,3 +1,7 @@ +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { CsprngArray } from "@bitwarden/common/types/csprng"; +import { UserId } from "@bitwarden/common/types/guid"; +import { UserKey } from "@bitwarden/common/types/key"; import { BiometricsStatus } from "@bitwarden/key-management"; import { RendererBiometricsService } from "./renderer-biometrics.service"; @@ -41,4 +45,34 @@ describe("renderer biometrics service tests", function () { expect(result).toBe(expected); }); }); + + describe("unlockWithBiometricsForUser", () => { + const testUserId = "userId1" as UserId; + const service = new RendererBiometricsService(); + + it("should return null if no user key is returned", async () => { + (global as any).ipc.keyManagement.biometric.unlockWithBiometricsForUser.mockResolvedValue( + null, + ); + + const result = await service.unlockWithBiometricsForUser(testUserId); + + expect(result).toBeNull(); + }); + + it("should return a UserKey object when a user key is returned", async () => { + const mockRandomBytes = new Uint8Array(64) as CsprngArray; + const mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey; + (global as any).ipc.keyManagement.biometric.unlockWithBiometricsForUser.mockResolvedValue( + mockUserKey.toJSON(), + ); + + const result = await service.unlockWithBiometricsForUser(testUserId); + + expect(result).not.toBeNull(); + expect(result).toBeInstanceOf(SymmetricCryptoKey); + expect(result!.keyB64).toEqual(mockUserKey.keyB64); + expect(result!.inner()).toEqual(mockUserKey.inner()); + }); + }); }); diff --git a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts index db17ee480cb..1404d65ae51 100644 --- a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts @@ -1,5 +1,6 @@ import { Injectable } from "@angular/core"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; import { BiometricsStatus } from "@bitwarden/key-management"; @@ -21,7 +22,12 @@ export class RendererBiometricsService extends DesktopBiometricsService { } async unlockWithBiometricsForUser(userId: UserId): Promise<UserKey | null> { - return await ipc.keyManagement.biometric.unlockWithBiometricsForUser(userId); + const userKey = await ipc.keyManagement.biometric.unlockWithBiometricsForUser(userId); + if (userKey == null) { + return null; + } + // Objects received over IPC lose their prototype, so they must be recreated to restore methods and properties. + return SymmetricCryptoKey.fromJSON(userKey) as UserKey; } async getBiometricsStatusForUser(id: UserId): Promise<BiometricsStatus> { diff --git a/apps/desktop/src/key-management/electron-key.service.spec.ts b/apps/desktop/src/key-management/electron-key.service.spec.ts new file mode 100644 index 00000000000..7a0464f5e27 --- /dev/null +++ b/apps/desktop/src/key-management/electron-key.service.spec.ts @@ -0,0 +1,188 @@ +import { mock } from "jest-mock-extended"; + +import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; +import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { CsprngArray } from "@bitwarden/common/types/csprng"; +import { UserId } from "@bitwarden/common/types/guid"; +import { UserKey } from "@bitwarden/common/types/key"; +import { BiometricStateService, KdfConfigService } from "@bitwarden/key-management"; + +import { + makeEncString, + makeStaticByteArray, + makeSymmetricCryptoKey, + FakeAccountService, + mockAccountServiceWith, + FakeStateProvider, +} from "../../../../libs/common/spec"; + +import { DesktopBiometricsService } from "./biometrics/desktop.biometrics.service"; +import { ElectronKeyService } from "./electron-key.service"; + +describe("ElectronKeyService", () => { + let keyService: ElectronKeyService; + + const pinService = mock<PinServiceAbstraction>(); + const keyGenerationService = mock<KeyGenerationService>(); + const cryptoFunctionService = mock<CryptoFunctionService>(); + const encryptService = mock<EncryptService>(); + const platformUtilService = mock<PlatformUtilsService>(); + const logService = mock<LogService>(); + const stateService = mock<StateService>(); + const kdfConfigService = mock<KdfConfigService>(); + const biometricStateService = mock<BiometricStateService>(); + const biometricService = mock<DesktopBiometricsService>(); + let stateProvider: FakeStateProvider; + + const mockUserId = Utils.newGuid() as UserId; + let accountService: FakeAccountService; + let masterPasswordService: FakeMasterPasswordService; + + beforeEach(() => { + accountService = mockAccountServiceWith(mockUserId); + masterPasswordService = new FakeMasterPasswordService(); + stateProvider = new FakeStateProvider(accountService); + + keyService = new ElectronKeyService( + pinService, + masterPasswordService, + keyGenerationService, + cryptoFunctionService, + encryptService, + platformUtilService, + logService, + stateService, + accountService, + stateProvider, + biometricStateService, + kdfConfigService, + biometricService, + ); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("setUserKey", () => { + const userKey = makeSymmetricCryptoKey() as UserKey; + + describe("store biometric key", () => { + it("does not set any biometric keys when biometric unlock disabled", async () => { + biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(false); + + await keyService.setUserKey(userKey, mockUserId); + + expect(biometricService.setClientKeyHalfForUser).not.toHaveBeenCalled(); + expect(biometricService.setBiometricProtectedUnlockKeyForUser).not.toHaveBeenCalled(); + expect(biometricStateService.setEncryptedClientKeyHalf).not.toHaveBeenCalled(); + expect(biometricStateService.getBiometricUnlockEnabled).toHaveBeenCalledWith(mockUserId); + }); + + describe("biometric unlock enabled", () => { + beforeEach(() => { + biometricStateService.getBiometricUnlockEnabled.mockResolvedValue(true); + }); + + it("sets null biometric client key half and biometric unlock key when require password on start disabled", async () => { + biometricStateService.getRequirePasswordOnStart.mockResolvedValue(false); + + await keyService.setUserKey(userKey, mockUserId); + + expect(biometricService.setClientKeyHalfForUser).toHaveBeenCalledWith(mockUserId, null); + expect(biometricService.setBiometricProtectedUnlockKeyForUser).toHaveBeenCalledWith( + mockUserId, + userKey.keyB64, + ); + expect(biometricStateService.setEncryptedClientKeyHalf).not.toHaveBeenCalled(); + expect(biometricStateService.getBiometricUnlockEnabled).toHaveBeenCalledWith(mockUserId); + expect(biometricStateService.getRequirePasswordOnStart).toHaveBeenCalledWith(mockUserId); + }); + + describe("require password on start enabled", () => { + beforeEach(() => { + biometricStateService.getRequirePasswordOnStart.mockResolvedValue(true); + }); + + it("sets new biometric client key half and biometric unlock key when no biometric client key half stored", async () => { + const clientKeyHalfBytes = makeStaticByteArray(32); + const clientKeyHalf = Utils.fromBufferToUtf8(clientKeyHalfBytes); + const encryptedClientKeyHalf = makeEncString(); + biometricStateService.getEncryptedClientKeyHalf.mockResolvedValue(null); + cryptoFunctionService.randomBytes.mockResolvedValue( + clientKeyHalfBytes.buffer as CsprngArray, + ); + encryptService.encryptString.mockResolvedValue(encryptedClientKeyHalf); + + await keyService.setUserKey(userKey, mockUserId); + + expect(biometricService.setClientKeyHalfForUser).toHaveBeenCalledWith( + mockUserId, + clientKeyHalf, + ); + expect(biometricService.setBiometricProtectedUnlockKeyForUser).toHaveBeenCalledWith( + mockUserId, + userKey.keyB64, + ); + expect(biometricStateService.setEncryptedClientKeyHalf).toHaveBeenCalledWith( + encryptedClientKeyHalf, + mockUserId, + ); + expect(biometricStateService.getBiometricUnlockEnabled).toHaveBeenCalledWith( + mockUserId, + ); + expect(biometricStateService.getRequirePasswordOnStart).toHaveBeenCalledWith( + mockUserId, + ); + expect(biometricStateService.getEncryptedClientKeyHalf).toHaveBeenCalledWith( + mockUserId, + ); + expect(cryptoFunctionService.randomBytes).toHaveBeenCalledWith(32); + expect(encryptService.encryptString).toHaveBeenCalledWith(clientKeyHalf, userKey); + }); + + it("sets decrypted biometric client key half and biometric unlock key when existing biometric client key half stored", async () => { + const encryptedClientKeyHalf = makeEncString(); + const clientKeyHalf = Utils.fromBufferToUtf8(makeStaticByteArray(32)); + biometricStateService.getEncryptedClientKeyHalf.mockResolvedValue( + encryptedClientKeyHalf, + ); + encryptService.decryptString.mockResolvedValue(clientKeyHalf); + + await keyService.setUserKey(userKey, mockUserId); + + expect(biometricService.setClientKeyHalfForUser).toHaveBeenCalledWith( + mockUserId, + clientKeyHalf, + ); + expect(biometricService.setBiometricProtectedUnlockKeyForUser).toHaveBeenCalledWith( + mockUserId, + userKey.keyB64, + ); + expect(biometricStateService.setEncryptedClientKeyHalf).not.toHaveBeenCalled(); + expect(biometricStateService.getBiometricUnlockEnabled).toHaveBeenCalledWith( + mockUserId, + ); + expect(biometricStateService.getRequirePasswordOnStart).toHaveBeenCalledWith( + mockUserId, + ); + expect(biometricStateService.getEncryptedClientKeyHalf).toHaveBeenCalledWith( + mockUserId, + ); + expect(encryptService.decryptString).toHaveBeenCalledWith( + encryptedClientKeyHalf, + userKey, + ); + }); + }); + }); + }); + }); +}); diff --git a/apps/desktop/src/platform/services/electron-key.service.ts b/apps/desktop/src/key-management/electron-key.service.ts similarity index 88% rename from apps/desktop/src/platform/services/electron-key.service.ts rename to apps/desktop/src/key-management/electron-key.service.ts index 5ecde57ec5b..2941276720c 100644 --- a/apps/desktop/src/platform/services/electron-key.service.ts +++ b/apps/desktop/src/key-management/electron-key.service.ts @@ -19,8 +19,9 @@ import { BiometricStateService, } from "@bitwarden/key-management"; -import { DesktopBiometricsService } from "../../key-management/biometrics/desktop.biometrics.service"; +import { DesktopBiometricsService } from "./biometrics/desktop.biometrics.service"; +// TODO Remove this class once biometric client key half storage is moved https://bitwarden.atlassian.net/browse/PM-22342 export class ElectronKeyService extends DefaultKeyService { constructor( pinService: PinServiceAbstraction, @@ -77,7 +78,6 @@ export class ElectronKeyService extends DefaultKeyService { private async storeBiometricsProtectedUserKey(userKey: UserKey, userId: UserId): Promise<void> { // May resolve to null, in which case no client key have is required - // TODO: Move to windows implementation const clientEncKeyHalf = await this.getBiometricEncryptionClientKeyHalf(userKey, userId); await this.biometricService.setClientKeyHalfForUser(userId, clientEncKeyHalf); await this.biometricService.setBiometricProtectedUnlockKeyForUser(userId, userKey.keyB64); @@ -102,11 +102,16 @@ export class ElectronKeyService extends DefaultKeyService { } // Retrieve existing key half if it exists - let clientKeyHalf = await this.biometricStateService - .getEncryptedClientKeyHalf(userId) - .then((result) => result?.decrypt(null /* user encrypted */, userKey)) - .then((result) => result as CsprngString); - if (clientKeyHalf == null && userKey != null) { + let clientKeyHalf: CsprngString | null = null; + const encryptedClientKeyHalf = + await this.biometricStateService.getEncryptedClientKeyHalf(userId); + if (encryptedClientKeyHalf != null) { + clientKeyHalf = (await this.encryptService.decryptString( + encryptedClientKeyHalf, + userKey, + )) as CsprngString; + } + if (clientKeyHalf == null) { // Set a key half if it doesn't exist const keyBytes = await this.cryptoFunctionService.randomBytes(32); clientKeyHalf = Utils.fromBufferToUtf8(keyBytes) as CsprngString; diff --git a/apps/desktop/src/key-management/preload.ts b/apps/desktop/src/key-management/preload.ts index c955571697b..3e90c27ab03 100644 --- a/apps/desktop/src/key-management/preload.ts +++ b/apps/desktop/src/key-management/preload.ts @@ -1,4 +1,5 @@ import { ipcRenderer } from "electron"; +import { Jsonify } from "type-fest"; import { UserKey } from "@bitwarden/common/types/key"; import { BiometricsStatus } from "@bitwarden/key-management"; @@ -14,7 +15,7 @@ const biometric = { ipcRenderer.invoke("biometric", { action: BiometricAction.GetStatus, } satisfies BiometricMessage), - unlockWithBiometricsForUser: (userId: string): Promise<UserKey | null> => + unlockWithBiometricsForUser: (userId: string): Promise<Jsonify<UserKey> | null> => ipcRenderer.invoke("biometric", { action: BiometricAction.UnlockForUser, userId: userId, diff --git a/libs/key-management-ui/src/lock/components/lock.component.ts b/libs/key-management-ui/src/lock/components/lock.component.ts index ef91f2a03f1..043e2333a9e 100644 --- a/libs/key-management-ui/src/lock/components/lock.component.ts +++ b/libs/key-management-ui/src/lock/components/lock.component.ts @@ -388,6 +388,8 @@ export class LockComponent implements OnInit, OnDestroy { return; } + this.logService.error("[LockComponent] Failed to unlock via biometrics.", e); + let biometricTranslatedErrorDesc; if (this.clientType === "browser") { From 6dabdd73cbf4efeb748dbe4e835ec1e332e8467e Mon Sep 17 00:00:00 2001 From: Jonathan Prusik <jprusik@users.noreply.github.com> Date: Tue, 3 Jun 2025 16:38:51 -0400 Subject: [PATCH 231/245] replace Autofill-owned enums (#15031) --- .../abstractions/overlay.background.ts | 4 +- .../background/overlay.background.spec.ts | 14 +++--- .../autofill/background/overlay.background.ts | 18 ++++---- .../lit-stories/.lit-docs/action-button.mdx | 2 +- .../lit-stories/.lit-docs/badge-button.mdx | 2 +- .../components/lit-stories/.lit-docs/body.mdx | 2 +- .../lit-stories/.lit-docs/close-button.mdx | 2 +- .../lit-stories/.lit-docs/edit-button.mdx | 2 +- .../lit-stories/.lit-docs/footer.mdx | 2 +- .../lit-stories/.lit-docs/header.mdx | 2 +- .../lit-stories/.lit-docs/icons.mdx | 2 +- .../autofill/enums/autofill-overlay.enum.ts | 18 ++++---- .../content/fido2-content-script.spec.ts | 22 +++++----- .../fido2/content/fido2-content-script.ts | 16 ++++--- .../fido2/content/fido2-page-script.ts | 18 ++++---- ...do2-page-script.webauthn-supported.spec.ts | 8 ++-- ...2-page-script.webauthn-unsupported.spec.ts | 8 ++-- .../fido2/content/messaging/message.ts | 44 +++++++++---------- .../fido2/content/messaging/messenger.ts | 12 ++--- .../src/autofill/models/autofill-field.ts | 4 +- .../abstractions/autofill-inline-menu-list.ts | 4 +- .../pages/list/autofill-inline-menu-list.ts | 8 ++-- .../autofill-overlay-content.service.spec.ts | 6 +-- .../autofill-overlay-content.service.ts | 14 +++--- 24 files changed, 118 insertions(+), 116 deletions(-) diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index 6ad9b8e06fd..5e2b755ad4a 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -4,7 +4,7 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { InlineMenuFillTypes } from "../../enums/autofill-overlay.enum"; +import { InlineMenuFillType } from "../../enums/autofill-overlay.enum"; import AutofillPageDetails from "../../models/autofill-page-details"; import { PageDetail } from "../../services/abstractions/autofill.service"; @@ -43,7 +43,7 @@ export type UpdateOverlayCiphersParams = { export type FocusedFieldData = { focusedFieldStyles: Partial<CSSStyleDeclaration>; focusedFieldRects: Partial<DOMRect>; - inlineMenuFillType?: InlineMenuFillTypes; + inlineMenuFillType?: InlineMenuFillType; tabId?: number; frameId?: number; accountCreationFieldType?: string; diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 0fe4a459048..92b2135c973 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -45,7 +45,7 @@ import { AutofillOverlayElement, AutofillOverlayPort, InlineMenuAccountCreationFieldType, - InlineMenuFillType, + InlineMenuFillTypes, MAX_SUB_FRAME_DEPTH, RedirectFocusDirection, } from "../enums/autofill-overlay.enum"; @@ -1025,7 +1025,7 @@ describe("OverlayBackground", () => { overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ tabId: tab.id, accountCreationFieldType: "text", - inlineMenuFillType: InlineMenuFillType.AccountCreationUsername, + inlineMenuFillType: InlineMenuFillTypes.AccountCreationUsername, }); cipherService.getAllDecryptedForUrl.mockResolvedValue([loginCipher1, identityCipher]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); @@ -1383,7 +1383,7 @@ describe("OverlayBackground", () => { { command: "updateFocusedFieldData", focusedFieldData: createFocusedFieldDataMock({ - inlineMenuFillType: InlineMenuFillType.CurrentPasswordUpdate, + inlineMenuFillType: InlineMenuFillTypes.CurrentPasswordUpdate, }), }, mock<chrome.runtime.MessageSender>({ tab }), @@ -2045,7 +2045,7 @@ describe("OverlayBackground", () => { }); it("displays the password generator when the focused field is for password generation", async () => { - focusedFieldData.inlineMenuFillType = InlineMenuFillType.PasswordGeneration; + focusedFieldData.inlineMenuFillType = InlineMenuFillTypes.PasswordGeneration; sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }, sender); await flushPromises(); @@ -2103,7 +2103,7 @@ describe("OverlayBackground", () => { }); it("shows the save login menu when the focused field type is for password generation and the field is filled", async () => { - focusedFieldData.inlineMenuFillType = InlineMenuFillType.PasswordGeneration; + focusedFieldData.inlineMenuFillType = InlineMenuFillTypes.PasswordGeneration; sendMockExtensionMessage( { command: "updateFocusedFieldData", focusedFieldData, focusedFieldHasValue: true }, @@ -3409,7 +3409,7 @@ describe("OverlayBackground", () => { { command: "updateFocusedFieldData", focusedFieldData: createFocusedFieldDataMock({ - inlineMenuFillType: InlineMenuFillType.CurrentPasswordUpdate, + inlineMenuFillType: InlineMenuFillTypes.CurrentPasswordUpdate, }), }, sender, @@ -3607,7 +3607,7 @@ describe("OverlayBackground", () => { describe("fillGeneratedPassword", () => { const focusedFieldData = createFocusedFieldDataMock({ - inlineMenuFillType: InlineMenuFillType.PasswordGeneration, + inlineMenuFillType: InlineMenuFillTypes.PasswordGeneration, }); beforeEach(() => { diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index ab5dd4abb8f..ce0dbe5bb23 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -797,7 +797,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param focusedFieldData - Optional focused field data to validate against */ private focusedFieldMatchesFillType( - fillType: InlineMenuFillTypes, + fillType: InlineMenuFillType, focusedFieldData?: FocusedFieldData, ) { const focusedFieldFillType = focusedFieldData @@ -806,7 +806,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { // When updating the current password for a field, it should fill with a login cipher if ( - focusedFieldFillType === InlineMenuFillType.CurrentPasswordUpdate && + focusedFieldFillType === InlineMenuFillTypes.CurrentPasswordUpdate && fillType === CipherType.Login ) { return true; @@ -819,7 +819,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { * Identifies whether the inline menu is being shown on an account creation field. */ private shouldShowInlineMenuAccountCreation(): boolean { - if (this.focusedFieldMatchesFillType(InlineMenuFillType.AccountCreationUsername)) { + if (this.focusedFieldMatchesFillType(InlineMenuFillTypes.AccountCreationUsername)) { return true; } @@ -1152,7 +1152,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { } let pageDetails = Array.from(pageDetailsForTab.values()); - if (this.focusedFieldMatchesFillType(InlineMenuFillType.CurrentPasswordUpdate)) { + if (this.focusedFieldMatchesFillType(InlineMenuFillTypes.CurrentPasswordUpdate)) { pageDetails = this.getFilteredPageDetails( pageDetails, this.inlineMenuFieldQualificationService.isUpdateCurrentPasswordField, @@ -1705,7 +1705,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private shouldUpdatePasswordGeneratorMenuOnFieldFocus() { return ( this.isInlineMenuButtonVisible && - this.focusedFieldMatchesFillType(InlineMenuFillType.PasswordGeneration) + this.focusedFieldMatchesFillType(InlineMenuFillTypes.PasswordGeneration) ); } @@ -1767,9 +1767,9 @@ export class OverlayBackground implements OverlayBackgroundInterface { private shouldUpdateAccountCreationMenuOnFieldFocus(previousFocusedFieldData: FocusedFieldData) { const accountCreationFieldBlurred = this.focusedFieldMatchesFillType( - InlineMenuFillType.AccountCreationUsername, + InlineMenuFillTypes.AccountCreationUsername, previousFocusedFieldData, - ) && !this.focusedFieldMatchesFillType(InlineMenuFillType.AccountCreationUsername); + ) && !this.focusedFieldMatchesFillType(InlineMenuFillTypes.AccountCreationUsername); return accountCreationFieldBlurred || this.shouldShowInlineMenuAccountCreation(); } @@ -1876,7 +1876,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { return ( (this.shouldShowInlineMenuAccountCreation() || - this.focusedFieldMatchesFillType(InlineMenuFillType.PasswordGeneration)) && + this.focusedFieldMatchesFillType(InlineMenuFillTypes.PasswordGeneration)) && !!(loginData.password || loginData.newPassword) ); } @@ -3036,7 +3036,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { } const focusFieldShouldShowPasswordGenerator = - this.focusedFieldMatchesFillType(InlineMenuFillType.PasswordGeneration) || + this.focusedFieldMatchesFillType(InlineMenuFillTypes.PasswordGeneration) || (showInlineMenuAccountCreation && this.focusedFieldMatchesAccountCreationType(InlineMenuAccountCreationFieldType.Password)); if (!focusFieldShouldShowPasswordGenerator) { diff --git a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/action-button.mdx b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/action-button.mdx index d3c1968b32f..73cd6fb93a9 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/action-button.mdx +++ b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/action-button.mdx @@ -20,7 +20,7 @@ It is designed with accessibility and responsive design in mind. | `buttonAction` | `(e: Event) => void` | Yes | The function to execute when the button is clicked. | | `buttonText` | `string` | Yes | The text to display on the button. | | `disabled` | `boolean` (default: false) | No | Disables the button when set to `true`. | -| `theme` | `Theme` | Yes | The theme to style the button. Must match the `Theme` enum. | +| `theme` | `Theme` | Yes | The theme to style the button. Must match the `Theme` type. | ## Installation and Setup diff --git a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/badge-button.mdx b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/badge-button.mdx index e0740ced760..47d82ad68da 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/badge-button.mdx +++ b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/badge-button.mdx @@ -20,7 +20,7 @@ handling, and a disabled state. The component is optimized for accessibility and | `buttonAction` | `(e: Event) => void` | Yes | The function to execute when the button is clicked. | | `buttonText` | `string` | Yes | The text to display on the badge button. | | `disabled` | `boolean` (default: false) | No | Disables the button when set to `true`. | -| `theme` | `Theme` | Yes | The theme to style the button. Must match the `Theme` enum. | +| `theme` | `Theme` | Yes | The theme to style the button. Must match the `Theme` type. | ## Installation and Setup diff --git a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/body.mdx b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/body.mdx index 3a6a955e286..a298594e17f 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/body.mdx +++ b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/body.mdx @@ -19,7 +19,7 @@ presenting actionable information. | ------------------ | ------------------ | ------------ | --------------------------------------------------------------------------------------------------------- | | `ciphers` | `CipherData[]` | Yes | An array of cipher data objects. Each cipher includes metadata such as ID, name, type, and login details. | | `notificationType` | `NotificationType` | Yes | Specifies the type of notification, such as `add`, `change`, `unlock`, or `fileless-import`. | -| `theme` | `Theme` | Yes | Defines the theme used for styling the notification. Must match the `Theme` enum. | +| `theme` | `Theme` | Yes | Defines the theme used for styling the notification. Must match the `Theme` type. | --- diff --git a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/close-button.mdx b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/close-button.mdx index dcdd38710ba..da9c15246fd 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/close-button.mdx +++ b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/close-button.mdx @@ -17,7 +17,7 @@ a close icon for visual clarity. The component is designed to be intuitive and a | **Prop** | **Type** | **Required** | **Description** | | ------------------------- | -------------------- | ------------ | ----------------------------------------------------------- | | `handleCloseNotification` | `(e: Event) => void` | Yes | The function to execute when the button is clicked. | -| `theme` | `Theme` | Yes | The theme to style the button. Must match the `Theme` enum. | +| `theme` | `Theme` | Yes | The theme to style the button. Must match the `Theme` type. | ## Installation and Setup diff --git a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/edit-button.mdx b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/edit-button.mdx index 0f38df18912..c6c4262806b 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/edit-button.mdx +++ b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/edit-button.mdx @@ -20,7 +20,7 @@ or settings where inline editing is required. | `buttonAction` | `(e: Event) => void` | Yes | The function to execute when the button is clicked. | | `buttonText` | `string` | Yes | The text displayed as the button's tooltip. | | `disabled` | `boolean` (default: false) | No | Disables the button when set to `true`. | -| `theme` | `Theme` | Yes | The theme to style the button. Must match the `Theme` enum. | +| `theme` | `Theme` | Yes | The theme to style the button. Must match the `Theme` type. | ## Installation and Setup diff --git a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/footer.mdx b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/footer.mdx index baaad4d8151..6a816f811e0 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/footer.mdx +++ b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/footer.mdx @@ -17,7 +17,7 @@ customization based on the `theme` and `notificationType`. | **Prop** | **Type** | **Required** | **Description** | | ------------------ | ------------------ | ------------ | -------------------------------------------------------------------------------------------------- | | `notificationType` | `NotificationType` | Yes | The type of notification footer to display. Options: `add`, `change`, `unlock`, `fileless-import`. | -| `theme` | `Theme` | Yes | Defines the theme of the notification footer. Must match the `Theme` enum. | +| `theme` | `Theme` | Yes | Defines the theme of the notification footer. Must match the `Theme` type. | --- diff --git a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/header.mdx b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/header.mdx index fd03fd2f950..ebe35a3dd9b 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/header.mdx +++ b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/header.mdx @@ -19,7 +19,7 @@ and an optional close button. This component is versatile and can be styled dyna | ------------------------- | -------------------- | ------------ | ------------------------------------------------------------------- | | `message` | `string` | Yes | The text message to be displayed in the notification. | | `standalone` | `boolean` | No | Determines if the notification is displayed independently. | -| `theme` | `Theme` | Yes | Defines the theme of the notification. Must match the `Theme` enum. | +| `theme` | `Theme` | Yes | Defines the theme of the notification. Must match the `Theme` type. | | `handleCloseNotification` | `(e: Event) => void` | No | A callback function triggered when the close button is clicked. | --- diff --git a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/icons.mdx b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/icons.mdx index 571ed10285a..7ec18d0f7bb 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/icons.mdx +++ b/apps/browser/src/autofill/content/components/lit-stories/.lit-docs/icons.mdx @@ -28,7 +28,7 @@ like size, color, and theme. Each story is an example of how a specific icon can | `iconLink` | `URL` | No | Defines an external URL associated with the icon, prop exclusive to `Brand Icon`. | | `color` | `string` | No | Sets the color of the icon. | | `disabled` | `boolean` | No | Disables the icon visually and functionally. | -| `theme` | `Theme` | Yes | Defines the theme used to style the icons. Must match the `Theme` enum. | +| `theme` | `Theme` | Yes | Defines the theme used to style the icons. Must match the `Theme` type. | | `size` | `number` | Yes | Sets the width and height of the icon in pixels. | --- diff --git a/apps/browser/src/autofill/enums/autofill-overlay.enum.ts b/apps/browser/src/autofill/enums/autofill-overlay.enum.ts index d0b970671a8..4e4b32b9038 100644 --- a/apps/browser/src/autofill/enums/autofill-overlay.enum.ts +++ b/apps/browser/src/autofill/enums/autofill-overlay.enum.ts @@ -21,14 +21,16 @@ export const RedirectFocusDirection = { Next: "next", } as const; -// FIXME: update to use a const object instead of a typescript enum -// eslint-disable-next-line @bitwarden/platform/no-enums -export enum InlineMenuFillType { - AccountCreationUsername = 5, - PasswordGeneration = 6, - CurrentPasswordUpdate = 7, -} -export type InlineMenuFillTypes = InlineMenuFillType | CipherType; +export const InlineMenuFillTypes = { + AccountCreationUsername: 5, + PasswordGeneration: 6, + CurrentPasswordUpdate: 7, +} as const; + +export type InlineMenuFillTypeValue = + (typeof InlineMenuFillTypes)[keyof typeof InlineMenuFillTypes]; + +export type InlineMenuFillType = InlineMenuFillTypeValue | CipherType; export const InlineMenuAccountCreationFieldType = { Text: "text", diff --git a/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts index 8885ed6299c..af7344beb66 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts @@ -6,7 +6,7 @@ import { createPortSpyMock } from "../../../autofill/spec/autofill-mocks"; import { triggerPortOnDisconnectEvent } from "../../../autofill/spec/testing-utils"; import { Fido2PortName } from "../enums/fido2-port-name.enum"; -import { InsecureCreateCredentialParams, MessageType } from "./messaging/message"; +import { InsecureCreateCredentialParams, MessageTypes } from "./messaging/message"; import { MessageWithMetadata, Messenger } from "./messaging/messenger"; jest.mock("../../../autofill/utils", () => ({ @@ -71,7 +71,7 @@ describe("Fido2 Content Script", () => { it("handles a FIDO2 credential creation request message from the window message listener, formats the message and sends the formatted message to the extension background", async () => { const message = mock<MessageWithMetadata>({ - type: MessageType.CredentialCreationRequest, + type: MessageTypes.CredentialCreationRequest, data: mock<InsecureCreateCredentialParams>(), }); const mockResult = { credentialId: "mock" } as CreateCredentialResult; @@ -92,14 +92,14 @@ describe("Fido2 Content Script", () => { requestId: expect.any(String), }); expect(response).toEqual({ - type: MessageType.CredentialCreationResponse, + type: MessageTypes.CredentialCreationResponse, result: mockResult, }); }); it("handles a FIDO2 credential get request message from the window message listener, formats the message and sends the formatted message to the extension background", async () => { const message = mock<MessageWithMetadata>({ - type: MessageType.CredentialGetRequest, + type: MessageTypes.CredentialGetRequest, data: mock<InsecureCreateCredentialParams>(), }); @@ -121,7 +121,7 @@ describe("Fido2 Content Script", () => { it("removes the abort handler when the FIDO2 request is complete", async () => { const message = mock<MessageWithMetadata>({ - type: MessageType.CredentialCreationRequest, + type: MessageTypes.CredentialCreationRequest, data: mock<InsecureCreateCredentialParams>(), }); const abortController = new AbortController(); @@ -138,16 +138,14 @@ describe("Fido2 Content Script", () => { it("sends an extension message to abort the FIDO2 request when the abort controller is signaled", async () => { const message = mock<MessageWithMetadata>({ - type: MessageType.CredentialCreationRequest, + type: MessageTypes.CredentialCreationRequest, data: mock<InsecureCreateCredentialParams>(), }); const abortController = new AbortController(); const abortSpy = jest.spyOn(abortController.signal, "addEventListener"); - jest - .spyOn(chrome.runtime, "sendMessage") - .mockImplementationOnce(async (extensionId: string, message: unknown, options: any) => { - abortController.abort(); - }); + jest.spyOn(chrome.runtime, "sendMessage").mockImplementationOnce(async () => { + abortController.abort(); + }); // FIXME: Remove when updating file. Eslint update // eslint-disable-next-line @typescript-eslint/no-require-imports @@ -165,7 +163,7 @@ describe("Fido2 Content Script", () => { it("rejects credential requests and returns an error result", async () => { const errorMessage = "Test error"; const message = mock<MessageWithMetadata>({ - type: MessageType.CredentialCreationRequest, + type: MessageTypes.CredentialCreationRequest, data: mock<InsecureCreateCredentialParams>(), }); const abortController = new AbortController(); diff --git a/apps/browser/src/autofill/fido2/content/fido2-content-script.ts b/apps/browser/src/autofill/fido2/content/fido2-content-script.ts index f8352fc27a6..03816f2b382 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-content-script.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-content-script.ts @@ -12,7 +12,7 @@ import { InsecureAssertCredentialParams, InsecureCreateCredentialParams, Message, - MessageType, + MessageTypes, } from "./messaging/message"; import { MessageWithMetadata, Messenger } from "./messaging/messenger"; @@ -49,21 +49,21 @@ import { MessageWithMetadata, Messenger } from "./messaging/messenger"; abortController.signal.addEventListener("abort", abortHandler); try { - if (message.type === MessageType.CredentialCreationRequest) { + if (message.type === MessageTypes.CredentialCreationRequest) { return handleCredentialCreationRequestMessage( requestId, message.data as InsecureCreateCredentialParams, ); } - if (message.type === MessageType.CredentialGetRequest) { + if (message.type === MessageTypes.CredentialGetRequest) { return handleCredentialGetRequestMessage( requestId, message.data as InsecureAssertCredentialParams, ); } - if (message.type === MessageType.AbortRequest) { + if (message.type === MessageTypes.AbortRequest) { return sendExtensionMessage("fido2AbortRequest", { abortedRequestId: requestId }); } } finally { @@ -83,7 +83,7 @@ import { MessageWithMetadata, Messenger } from "./messaging/messenger"; ): Promise<Message | undefined> { return respondToCredentialRequest( "fido2RegisterCredentialRequest", - MessageType.CredentialCreationResponse, + MessageTypes.CredentialCreationResponse, requestId, data, ); @@ -101,7 +101,7 @@ import { MessageWithMetadata, Messenger } from "./messaging/messenger"; ): Promise<Message | undefined> { return respondToCredentialRequest( "fido2GetCredentialRequest", - MessageType.CredentialGetResponse, + MessageTypes.CredentialGetResponse, requestId, data, ); @@ -118,7 +118,9 @@ import { MessageWithMetadata, Messenger } from "./messaging/messenger"; */ async function respondToCredentialRequest( command: string, - type: MessageType.CredentialCreationResponse | MessageType.CredentialGetResponse, + type: + | typeof MessageTypes.CredentialCreationResponse + | typeof MessageTypes.CredentialGetResponse, requestId: string, messageData: InsecureCreateCredentialParams | InsecureAssertCredentialParams, ): Promise<Message | undefined> { diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts index 4c1761c37ba..5b9ea5e5b27 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { WebauthnUtils } from "../utils/webauthn-utils"; -import { MessageType } from "./messaging/message"; +import { MessageTypes } from "./messaging/message"; import { Messenger } from "./messaging/messenger"; (function (globalContext) { @@ -100,13 +100,13 @@ import { Messenger } from "./messaging/messenger"; try { const response = await messenger.request( { - type: MessageType.CredentialCreationRequest, + type: MessageTypes.CredentialCreationRequest, data: WebauthnUtils.mapCredentialCreationOptions(options, fallbackSupported), }, options?.signal, ); - if (response.type !== MessageType.CredentialCreationResponse) { + if (response.type !== MessageTypes.CredentialCreationResponse) { throw new Error("Something went wrong."); } @@ -141,19 +141,19 @@ import { Messenger } from "./messaging/messenger"; try { const abortListener = () => messenger.request({ - type: MessageType.AbortRequest, + type: MessageTypes.AbortRequest, abortedRequestId: abortSignal.toString(), }); internalAbortController.signal.addEventListener("abort", abortListener); const response = await messenger.request( { - type: MessageType.CredentialGetRequest, + type: MessageTypes.CredentialGetRequest, data: WebauthnUtils.mapCredentialRequestOptions(options, fallbackSupported), }, internalAbortController.signal, ); internalAbortController.signal.removeEventListener("abort", abortListener); - if (response.type !== MessageType.CredentialGetResponse) { + if (response.type !== MessageTypes.CredentialGetResponse) { throw new Error("Something went wrong."); } @@ -182,13 +182,13 @@ import { Messenger } from "./messaging/messenger"; try { const response = await messenger.request( { - type: MessageType.CredentialGetRequest, + type: MessageTypes.CredentialGetRequest, data: WebauthnUtils.mapCredentialRequestOptions(options, fallbackSupported), }, options?.signal, ); - if (response.type !== MessageType.CredentialGetResponse) { + if (response.type !== MessageTypes.CredentialGetResponse) { throw new Error("Something went wrong."); } @@ -282,7 +282,7 @@ import { Messenger } from "./messaging/messenger"; const type = message.type; // Handle cleanup for disconnect request - if (type === MessageType.DisconnectRequest) { + if (type === MessageTypes.DisconnectRequest) { destroy(); } }; diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts index f1aec69193b..5e22027b584 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts @@ -7,7 +7,7 @@ import { } from "../../../autofill/spec/fido2-testing-utils"; import { WebauthnUtils } from "../utils/webauthn-utils"; -import { MessageType } from "./messaging/message"; +import { MessageTypes } from "./messaging/message"; import { Messenger } from "./messaging/messenger"; const originalGlobalThis = globalThis; @@ -71,7 +71,7 @@ describe("Fido2 page script with native WebAuthn support", () => { describe("creating WebAuthn credentials", () => { beforeEach(() => { messenger.request = jest.fn().mockResolvedValue({ - type: MessageType.CredentialCreationResponse, + type: MessageTypes.CredentialCreationResponse, result: mockCreateCredentialsResult, }); }); @@ -104,7 +104,7 @@ describe("Fido2 page script with native WebAuthn support", () => { describe("get WebAuthn credentials", () => { beforeEach(() => { messenger.request = jest.fn().mockResolvedValue({ - type: MessageType.CredentialGetResponse, + type: MessageTypes.CredentialGetResponse, result: mockCredentialAssertResult, }); }); @@ -147,7 +147,7 @@ describe("Fido2 page script with native WebAuthn support", () => { it("should destroy the message listener when receiving a disconnect request", async () => { jest.spyOn(globalThis.top, "removeEventListener"); const SENDER = "bitwarden-webauthn"; - void messenger.handler({ type: MessageType.DisconnectRequest, SENDER, senderId: "1" }); + void messenger.handler({ type: MessageTypes.DisconnectRequest, SENDER, senderId: "1" }); expect(globalThis.top.removeEventListener).toHaveBeenCalledWith("focus", undefined); expect(messenger.destroy).toHaveBeenCalled(); diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts index af1838ec942..d15bc475f57 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts @@ -6,7 +6,7 @@ import { } from "../../../autofill/spec/fido2-testing-utils"; import { WebauthnUtils } from "../utils/webauthn-utils"; -import { MessageType } from "./messaging/message"; +import { MessageTypes } from "./messaging/message"; import { Messenger } from "./messaging/messenger"; const originalGlobalThis = globalThis; @@ -65,7 +65,7 @@ describe("Fido2 page script without native WebAuthn support", () => { describe("creating WebAuthn credentials", () => { beforeEach(() => { messenger.request = jest.fn().mockResolvedValue({ - type: MessageType.CredentialCreationResponse, + type: MessageTypes.CredentialCreationResponse, result: mockCreateCredentialsResult, }); }); @@ -86,7 +86,7 @@ describe("Fido2 page script without native WebAuthn support", () => { describe("get WebAuthn credentials", () => { beforeEach(() => { messenger.request = jest.fn().mockResolvedValue({ - type: MessageType.CredentialGetResponse, + type: MessageTypes.CredentialGetResponse, result: mockCredentialAssertResult, }); }); @@ -108,7 +108,7 @@ describe("Fido2 page script without native WebAuthn support", () => { it("should destroy the message listener when receiving a disconnect request", async () => { jest.spyOn(globalThis.top, "removeEventListener"); const SENDER = "bitwarden-webauthn"; - void messenger.handler({ type: MessageType.DisconnectRequest, SENDER, senderId: "1" }); + void messenger.handler({ type: MessageTypes.DisconnectRequest, SENDER, senderId: "1" }); expect(globalThis.top.removeEventListener).toHaveBeenCalledWith("focus", undefined); expect(messenger.destroy).toHaveBeenCalled(); diff --git a/apps/browser/src/autofill/fido2/content/messaging/message.ts b/apps/browser/src/autofill/fido2/content/messaging/message.ts index 640af22ab9a..55dc522ceab 100644 --- a/apps/browser/src/autofill/fido2/content/messaging/message.ts +++ b/apps/browser/src/autofill/fido2/content/messaging/message.ts @@ -5,19 +5,19 @@ import { AssertCredentialResult, } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; -// FIXME: update to use a const object instead of a typescript enum -// eslint-disable-next-line @bitwarden/platform/no-enums -export enum MessageType { - CredentialCreationRequest, - CredentialCreationResponse, - CredentialGetRequest, - CredentialGetResponse, - AbortRequest, - DisconnectRequest, - ReconnectRequest, - AbortResponse, - ErrorResponse, -} +export const MessageTypes = { + CredentialCreationRequest: 0, + CredentialCreationResponse: 1, + CredentialGetRequest: 2, + CredentialGetResponse: 3, + AbortRequest: 4, + DisconnectRequest: 5, + ReconnectRequest: 6, + AbortResponse: 7, + ErrorResponse: 8, +} as const; + +export type MessageType = (typeof MessageTypes)[keyof typeof MessageTypes]; /** * The params provided by the page-script are created in an insecure environment and @@ -30,12 +30,12 @@ export type InsecureCreateCredentialParams = Omit< >; export type CredentialCreationRequest = { - type: MessageType.CredentialCreationRequest; + type: typeof MessageTypes.CredentialCreationRequest; data: InsecureCreateCredentialParams; }; export type CredentialCreationResponse = { - type: MessageType.CredentialCreationResponse; + type: typeof MessageTypes.CredentialCreationResponse; result?: CreateCredentialResult; }; @@ -50,35 +50,35 @@ export type InsecureAssertCredentialParams = Omit< >; export type CredentialGetRequest = { - type: MessageType.CredentialGetRequest; + type: typeof MessageTypes.CredentialGetRequest; data: InsecureAssertCredentialParams; }; export type CredentialGetResponse = { - type: MessageType.CredentialGetResponse; + type: typeof MessageTypes.CredentialGetResponse; result?: AssertCredentialResult; }; export type AbortRequest = { - type: MessageType.AbortRequest; + type: typeof MessageTypes.AbortRequest; abortedRequestId: string; }; export type DisconnectRequest = { - type: MessageType.DisconnectRequest; + type: typeof MessageTypes.DisconnectRequest; }; export type ReconnectRequest = { - type: MessageType.ReconnectRequest; + type: typeof MessageTypes.ReconnectRequest; }; export type ErrorResponse = { - type: MessageType.ErrorResponse; + type: typeof MessageTypes.ErrorResponse; error: string; }; export type AbortResponse = { - type: MessageType.AbortResponse; + type: typeof MessageTypes.AbortResponse; abortedRequestId: string; }; diff --git a/apps/browser/src/autofill/fido2/content/messaging/messenger.ts b/apps/browser/src/autofill/fido2/content/messaging/messenger.ts index ec7ff3bb7a4..257f7e9efd5 100644 --- a/apps/browser/src/autofill/fido2/content/messaging/messenger.ts +++ b/apps/browser/src/autofill/fido2/content/messaging/messenger.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Message, MessageType } from "./message"; +import { Message, MessageTypes } from "./message"; const SENDER = "bitwarden-webauthn"; @@ -80,7 +80,7 @@ export class Messenger { const abortListener = () => localPort.postMessage({ metadata: { SENDER }, - type: MessageType.AbortRequest, + type: MessageTypes.AbortRequest, }); abortSignal?.addEventListener("abort", abortListener); @@ -92,7 +92,7 @@ export class Messenger { abortSignal?.removeEventListener("abort", abortListener); - if (response.type === MessageType.ErrorResponse) { + if (response.type === MessageTypes.ErrorResponse) { const error = new Error(); Object.assign(error, JSON.parse(response.error)); throw error; @@ -119,7 +119,7 @@ export class Messenger { const abortController = new AbortController(); port.onmessage = (event: MessageEvent<MessageWithMetadata>) => { - if (event.data.type === MessageType.AbortRequest) { + if (event.data.type === MessageTypes.AbortRequest) { abortController.abort(); } }; @@ -133,7 +133,7 @@ export class Messenger { } catch (error) { port.postMessage({ SENDER, - type: MessageType.ErrorResponse, + type: MessageTypes.ErrorResponse, error: JSON.stringify(error, Object.getOwnPropertyNames(error)), }); } finally { @@ -157,7 +157,7 @@ export class Messenger { } private async sendDisconnectCommand() { - await this.request({ type: MessageType.DisconnectRequest }); + await this.request({ type: MessageTypes.DisconnectRequest }); } private generateUniqueId() { diff --git a/apps/browser/src/autofill/models/autofill-field.ts b/apps/browser/src/autofill/models/autofill-field.ts index c0be60f1cd0..1a8c3bb875b 100644 --- a/apps/browser/src/autofill/models/autofill-field.ts +++ b/apps/browser/src/autofill/models/autofill-field.ts @@ -4,7 +4,7 @@ import { FieldRect } from "../background/abstractions/overlay.background"; import { AutofillFieldQualifierType } from "../enums/autofill-field.enums"; import { InlineMenuAccountCreationFieldTypes, - InlineMenuFillTypes, + InlineMenuFillType, } from "../enums/autofill-overlay.enum"; /** @@ -118,7 +118,7 @@ export default class AutofillField { checked?: boolean; - inlineMenuFillType?: InlineMenuFillTypes; + inlineMenuFillType?: InlineMenuFillType; showPasskeys?: boolean; diff --git a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts index a20bd3c5312..f5e1fe08850 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts @@ -1,7 +1,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { InlineMenuCipherData } from "../../../background/abstractions/overlay.background"; -import { InlineMenuFillTypes } from "../../../enums/autofill-overlay.enum"; +import { InlineMenuFillType } from "../../../enums/autofill-overlay.enum"; type AutofillInlineMenuListMessage = { command: string }; @@ -23,7 +23,7 @@ export type InitAutofillInlineMenuListMessage = AutofillInlineMenuListMessage & theme: string; translations: Record<string, string>; ciphers?: InlineMenuCipherData[]; - inlineMenuFillType?: InlineMenuFillTypes; + inlineMenuFillType?: InlineMenuFillType; showInlineMenuAccountCreation?: boolean; showPasskeysLabels?: boolean; portKey: string; diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts index e0db93b6b4a..c680fe4745c 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts @@ -10,7 +10,7 @@ import { EVENTS, UPDATE_PASSKEYS_HEADINGS_ON_SCROLL } from "@bitwarden/common/au import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; import { InlineMenuCipherData } from "../../../../background/abstractions/overlay.background"; -import { InlineMenuFillTypes } from "../../../../enums/autofill-overlay.enum"; +import { InlineMenuFillType } from "../../../../enums/autofill-overlay.enum"; import { buildSvgDomElement, specialCharacterToKeyMap, throttle } from "../../../../utils"; import { creditCardIcon, @@ -42,7 +42,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { private cipherListScrollIsDebounced = false; private cipherListScrollDebounceTimeout: number | NodeJS.Timeout; private currentCipherIndex = 0; - private inlineMenuFillType: InlineMenuFillTypes; + private inlineMenuFillType: InlineMenuFillType; private showInlineMenuAccountCreation: boolean; private showPasskeysLabels: boolean; private newItemButtonElement: HTMLButtonElement; @@ -1105,8 +1105,8 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { const svgElement = buildSvgDomElement(` <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 29 29"> - <circle fill="none" cx="14.5" cy="14.5" r="12.5" - stroke-width="3" stroke-dasharray="78.5" + <circle fill="none" cx="14.5" cy="14.5" r="12.5" + stroke-width="3" stroke-dasharray="78.5" stroke-dashoffset="78.5" transform="rotate(-90 14.5 14.5)"></circle> <circle fill="none" cx="14.5" cy="14.5" r="14" stroke-width="1"></circle> </svg> diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts index 48bc7ceafda..730b002953b 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts @@ -6,7 +6,7 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import AutofillInit from "../content/autofill-init"; import { AutofillOverlayElement, - InlineMenuFillType, + InlineMenuFillTypes, MAX_SUB_FRAME_DEPTH, RedirectFocusDirection, } from "../enums/autofill-overlay.enum"; @@ -1383,7 +1383,7 @@ describe("AutofillOverlayContentService", () => { ); expect(autofillFieldElement.removeEventListener).toHaveBeenCalled(); expect(inputAccountFieldData.inlineMenuFillType).toEqual( - InlineMenuFillType.AccountCreationUsername, + InlineMenuFillTypes.AccountCreationUsername, ); }); @@ -1420,7 +1420,7 @@ describe("AutofillOverlayContentService", () => { await flushPromises(); expect(currentPasswordFieldData.inlineMenuFillType).toEqual( - InlineMenuFillType.CurrentPasswordUpdate, + InlineMenuFillTypes.CurrentPasswordUpdate, ); }); }); diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts index dc8f45d104b..1a972e0eaa0 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -24,7 +24,7 @@ import { AutofillFieldQualifier, AutofillFieldQualifierType } from "../enums/aut import { AutofillOverlayElement, InlineMenuAccountCreationFieldType, - InlineMenuFillType, + InlineMenuFillTypes, MAX_SUB_FRAME_DEPTH, RedirectFocusDirection, } from "../enums/autofill-overlay.enum"; @@ -789,11 +789,11 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ if (!autofillFieldData.fieldQualifier) { switch (autofillFieldData.inlineMenuFillType) { case CipherType.Login: - case InlineMenuFillType.CurrentPasswordUpdate: + case InlineMenuFillTypes.CurrentPasswordUpdate: this.qualifyUserFilledField(autofillFieldData, this.loginFieldQualifiers); break; - case InlineMenuFillType.AccountCreationUsername: - case InlineMenuFillType.PasswordGeneration: + case InlineMenuFillTypes.AccountCreationUsername: + case InlineMenuFillTypes.PasswordGeneration: this.qualifyUserFilledField(autofillFieldData, this.accountCreationFieldQualifiers); break; case CipherType.Card: @@ -1106,18 +1106,18 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ */ private setQualifiedAccountCreationFillType(autofillFieldData: AutofillField) { if (this.inlineMenuFieldQualificationService.isNewPasswordField(autofillFieldData)) { - autofillFieldData.inlineMenuFillType = InlineMenuFillType.PasswordGeneration; + autofillFieldData.inlineMenuFillType = InlineMenuFillTypes.PasswordGeneration; this.qualifyAccountCreationFieldType(autofillFieldData); return; } if (this.inlineMenuFieldQualificationService.isUpdateCurrentPasswordField(autofillFieldData)) { - autofillFieldData.inlineMenuFillType = InlineMenuFillType.CurrentPasswordUpdate; + autofillFieldData.inlineMenuFillType = InlineMenuFillTypes.CurrentPasswordUpdate; return; } if (this.inlineMenuFieldQualificationService.isUsernameField(autofillFieldData)) { - autofillFieldData.inlineMenuFillType = InlineMenuFillType.AccountCreationUsername; + autofillFieldData.inlineMenuFillType = InlineMenuFillTypes.AccountCreationUsername; this.qualifyAccountCreationFieldType(autofillFieldData); } } From 4223a7e2d79fd203ea89b69cdb4f627cb0369a4f Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 3 Jun 2025 13:59:34 -0700 Subject: [PATCH 232/245] [PM-22344] - update response type for shareManyWithServer (#15061) * update response type for shareManyWithServer * build new ListResponse --- libs/common/src/abstractions/api.service.ts | 2 +- libs/common/src/services/api.service.ts | 5 +++-- libs/common/src/vault/services/cipher.service.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index aba7454384d..44b5e34a4a4 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -208,7 +208,7 @@ export abstract class ApiService { deleteManyCiphersAdmin: (request: CipherBulkDeleteRequest) => Promise<any>; putMoveCiphers: (request: CipherBulkMoveRequest) => Promise<any>; putShareCipher: (id: string, request: CipherShareRequest) => Promise<CipherResponse>; - putShareCiphers: (request: CipherBulkShareRequest) => Promise<CipherResponse[]>; + putShareCiphers: (request: CipherBulkShareRequest) => Promise<ListResponse<CipherResponse>>; putCipherCollections: ( id: string, request: CipherCollectionsRequest, diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 4d40f814a2b..1971cd86363 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -532,8 +532,9 @@ export class ApiService implements ApiServiceAbstraction { return new CipherResponse(r); } - async putShareCiphers(request: CipherBulkShareRequest): Promise<CipherResponse[]> { - return await this.send("PUT", "/ciphers/share", request, true, true); + async putShareCiphers(request: CipherBulkShareRequest): Promise<ListResponse<CipherResponse>> { + const r = await this.send("PUT", "/ciphers/share", request, true, true); + return new ListResponse<CipherResponse>(r, CipherResponse); } async putCipherCollections( diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 13b94a2fed2..762b2bd3688 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -852,7 +852,7 @@ export class CipherService implements CipherServiceAbstraction { const request = new CipherBulkShareRequest(encCiphers, collectionIds, userId); try { const response = await this.apiService.putShareCiphers(request); - const responseMap = new Map(response.map((c) => [c.id, c])); + const responseMap = new Map(response.data.map((r) => [r.id, r])); encCiphers.forEach((cipher) => { const matchingCipher = responseMap.get(cipher.id); From 6ea944393b1d9468b557a71fb57c26da65a92a2e Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 3 Jun 2025 14:23:59 -0700 Subject: [PATCH 233/245] [PM-21904] - open claimed-accounts in new tab (#14981) * open claimed-accounts in new tab * add noopener to anchor --- .../src/app/auth/settings/account/profile.component.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/auth/settings/account/profile.component.html b/apps/web/src/app/auth/settings/account/profile.component.html index d2a887c9b98..74e9cf08f89 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.html +++ b/apps/web/src/app/auth/settings/account/profile.component.html @@ -38,7 +38,11 @@ </div> <div *ngIf="managingOrganization$ | async as managingOrganization"> {{ "accountIsOwnedMessage" | i18n: managingOrganization?.name }} - <a href="https://bitwarden.com/help/claimed-accounts"> + <a + target="_blank" + rel="noopener noreferrer" + href="https://bitwarden.com/help/claimed-accounts" + > <i class="bwi bwi-question-circle" aria-hidden="true"></i> </a> </div> From 9aaeacf2bedf4dfaae717bbdc6569fd510d46079 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Tue, 3 Jun 2025 23:52:53 +0200 Subject: [PATCH 234/245] [PM-22194] Remove key rotation v1 (#14945) --- .../settings/change-password.component.ts | 138 +--------------- .../user-key-rotation.service.spec.ts | 55 ------- .../key-rotation/user-key-rotation.service.ts | 153 +----------------- libs/common/src/enums/feature-flag.enum.ts | 2 - 4 files changed, 7 insertions(+), 341 deletions(-) diff --git a/apps/web/src/app/auth/settings/change-password.component.ts b/apps/web/src/app/auth/settings/change-password.component.ts index 1d95a498694..15d106057ba 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -12,16 +12,10 @@ import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/ma import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { HashPurpose } from "@bitwarden/common/platform/enums"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { UserId } from "@bitwarden/common/types/guid"; -import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; @@ -47,7 +41,6 @@ export class ChangePasswordComponent masterPasswordHint: string; checkForBreaches = true; characterMinimumMessage = ""; - userkeyRotationV2 = false; constructor( i18nService: I18nService, @@ -67,7 +60,6 @@ export class ChangePasswordComponent protected masterPasswordService: InternalMasterPasswordServiceAbstraction, accountService: AccountService, toastService: ToastService, - private configService: ConfigService, ) { super( i18nService, @@ -84,8 +76,6 @@ export class ChangePasswordComponent } async ngOnInit() { - this.userkeyRotationV2 = await this.configService.getFeatureFlag(FeatureFlag.UserKeyRotationV2); - if (!(await this.userVerificationService.hasMasterPassword())) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -148,22 +138,14 @@ export class ChangePasswordComponent } async submit() { - if (this.userkeyRotationV2) { - this.loading = true; - await this.submitNew(); - this.loading = false; - } else { - await this.submitOld(); - } - } - - async submitNew() { + this.loading = true; if (this.currentMasterPassword == null || this.currentMasterPassword === "") { this.toastService.showToast({ variant: "error", title: this.i18nService.t("errorOccurred"), message: this.i18nService.t("masterPasswordRequired"), }); + this.loading = false; return; } @@ -176,6 +158,7 @@ export class ChangePasswordComponent title: this.i18nService.t("errorOccurred"), message: this.i18nService.t("hintEqualsPassword"), }); + this.loading = false; return; } @@ -185,6 +168,7 @@ export class ChangePasswordComponent } if (!(await this.strongPassword())) { + this.loading = false; return; } @@ -207,6 +191,8 @@ export class ChangePasswordComponent title: this.i18nService.t("errorOccurred"), message: e.message, }); + } finally { + this.loading = false; } } @@ -270,116 +256,4 @@ export class ChangePasswordComponent }); } } - - async submitOld() { - if ( - this.masterPasswordHint != null && - this.masterPasswordHint.toLowerCase() === this.masterPassword.toLowerCase() - ) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("hintEqualsPassword"), - }); - return; - } - - this.leakedPassword = false; - if (this.checkForBreaches) { - this.leakedPassword = (await this.auditService.passwordLeaked(this.masterPassword)) > 0; - } - - await super.submit(); - } - - async setupSubmitActions() { - if (this.currentMasterPassword == null || this.currentMasterPassword === "") { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPasswordRequired"), - }); - return false; - } - - if (this.rotateUserKey) { - await this.syncService.fullSync(true); - } - - return super.setupSubmitActions(); - } - - async performSubmitActions( - newMasterPasswordHash: string, - newMasterKey: MasterKey, - newUserKey: [UserKey, EncString], - ) { - const [userId, email] = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])), - ); - - const masterKey = await this.keyService.makeMasterKey( - this.currentMasterPassword, - email, - await this.kdfConfigService.getKdfConfig(userId), - ); - - const newLocalKeyHash = await this.keyService.hashMasterKey( - this.masterPassword, - newMasterKey, - HashPurpose.LocalAuthorization, - ); - - const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(masterKey, userId); - if (userKey == null) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("invalidMasterPassword"), - }); - return; - } - - const request = new PasswordRequest(); - request.masterPasswordHash = await this.keyService.hashMasterKey( - this.currentMasterPassword, - masterKey, - ); - request.masterPasswordHint = this.masterPasswordHint; - request.newMasterPasswordHash = newMasterPasswordHash; - request.key = newUserKey[1].encryptedString; - - try { - if (this.rotateUserKey) { - this.formPromise = this.masterPasswordApiService.postPassword(request).then(async () => { - // we need to save this for local masterkey verification during rotation - await this.masterPasswordService.setMasterKeyHash(newLocalKeyHash, userId as UserId); - await this.masterPasswordService.setMasterKey(newMasterKey, userId as UserId); - return this.updateKey(); - }); - } else { - this.formPromise = this.masterPasswordApiService.postPassword(request); - } - - await this.formPromise; - - this.toastService.showToast({ - variant: "success", - title: this.i18nService.t("masterPasswordChanged"), - message: this.i18nService.t("logBackIn"), - }); - this.messagingService.send("logout"); - } catch { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - } - } - - private async updateKey() { - const user = await firstValueFrom(this.accountService.activeAccount$); - await this.keyRotationService.rotateUserKeyAndEncryptedDataLegacy(this.masterPassword, user); - } } diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts index c65c4ac3ea4..4dc5a206a63 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts @@ -244,21 +244,6 @@ describe("KeyRotationService", () => { mockWebauthnLoginAdminService.getRotatedData.mockResolvedValue(webauthn); }); - it("rotates the user key and encrypted data legacy", async () => { - await keyRotationService.rotateUserKeyAndEncryptedDataLegacy("mockMasterPassword", mockUser); - - expect(mockApiService.postUserKeyUpdate).toHaveBeenCalled(); - const arg = mockApiService.postUserKeyUpdate.mock.calls[0][0]; - expect(arg.key).toBe("mockNewUserKey"); - expect(arg.privateKey).toBe("mockEncryptedData"); - expect(arg.ciphers.length).toBe(2); - expect(arg.folders.length).toBe(2); - expect(arg.sends.length).toBe(2); - expect(arg.emergencyAccessKeys.length).toBe(1); - expect(arg.resetPasswordKeys.length).toBe(1); - expect(arg.webauthnKeys.length).toBe(2); - }); - it("rotates the userkey and encrypted data and changes master password", async () => { KeyRotationTrustInfoComponent.open = initialPromptedOpenTrue; EmergencyAccessTrustComponent.open = emergencyAccessTrustOpenTrusted; @@ -383,34 +368,12 @@ describe("KeyRotationService", () => { expect(mockApiService.postUserKeyUpdateV2).not.toHaveBeenCalled(); }); - it("legacy throws if master password provided is falsey", async () => { - await expect( - keyRotationService.rotateUserKeyAndEncryptedDataLegacy("", mockUser), - ).rejects.toThrow(); - }); - it("throws if master password provided is falsey", async () => { await expect( keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData("", "", mockUser), ).rejects.toThrow(); }); - it("legacy throws if user key creation fails", async () => { - mockKeyService.makeUserKey.mockResolvedValueOnce([null, null]); - - await expect( - keyRotationService.rotateUserKeyAndEncryptedDataLegacy("mockMasterPassword", mockUser), - ).rejects.toThrow(); - }); - - it("legacy throws if no private key is found", async () => { - privateKey.next(null); - - await expect( - keyRotationService.rotateUserKeyAndEncryptedDataLegacy("mockMasterPassword", mockUser), - ).rejects.toThrow(); - }); - it("throws if no private key is found", async () => { keyPair.next(null); @@ -423,16 +386,6 @@ describe("KeyRotationService", () => { ).rejects.toThrow(); }); - it("legacy throws if master password is incorrect", async () => { - mockUserVerificationService.verifyUserByMasterPassword.mockRejectedValueOnce( - new Error("Invalid master password"), - ); - - await expect( - keyRotationService.rotateUserKeyAndEncryptedDataLegacy("mockMasterPassword", mockUser), - ).rejects.toThrow(); - }); - it("throws if master password is incorrect", async () => { mockUserVerificationService.verifyUserByMasterPassword.mockRejectedValueOnce( new Error("Invalid master password"), @@ -447,14 +400,6 @@ describe("KeyRotationService", () => { ).rejects.toThrow(); }); - it("legacy throws if server rotation fails", async () => { - mockApiService.postUserKeyUpdate.mockRejectedValueOnce(new Error("mockError")); - - await expect( - keyRotationService.rotateUserKeyAndEncryptedDataLegacy("mockMasterPassword", mockUser), - ).rejects.toThrow(); - }); - it("throws if server rotation fails", async () => { KeyRotationTrustInfoComponent.open = initialPromptedOpenTrue; EmergencyAccessTrustComponent.open = emergencyAccessTrustOpenTrusted; diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts index 129d643f677..fc4ad0c869b 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts @@ -4,7 +4,6 @@ import { firstValueFrom } from "rxjs"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; -import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; @@ -14,10 +13,9 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { HashPurpose } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; -import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -39,7 +37,6 @@ import { AccountKeysRequest } from "./request/account-keys.request"; import { MasterPasswordUnlockDataRequest } from "./request/master-password-unlock-data.request"; import { RotateUserAccountKeysRequest } from "./request/rotate-user-account-keys.request"; import { UnlockDataRequest } from "./request/unlock-data.request"; -import { UpdateKeyRequest } from "./request/update-key.request"; import { UserDataRequest } from "./request/userdata.request"; import { UserKeyRotationApiService } from "./user-key-rotation-api.service"; @@ -302,152 +299,4 @@ export class UserKeyRotationService { // temporary until userkey can be better verified await this.vaultTimeoutService.logOut(); } - - /** - * Creates a new user key and re-encrypts all required data with the it. - * @param masterPassword current master password (used for validation) - * @deprecated - */ - async rotateUserKeyAndEncryptedDataLegacy(masterPassword: string, user: Account): Promise<void> { - this.logService.info("[Userkey rotation] Starting legacy user key rotation..."); - if (!masterPassword) { - this.logService.info("[Userkey rotation] Invalid master password provided. Aborting!"); - throw new Error("Invalid master password"); - } - - if ((await this.syncService.getLastSync()) === null) { - this.logService.info("[Userkey rotation] Client was never synced. Aborting!"); - throw new Error( - "The local vault is de-synced and the keys cannot be rotated. Please log out and log back in to resolve this issue.", - ); - } - - const emergencyAccessGrantees = await this.emergencyAccessService.getPublicKeys(); - const orgs = await this.resetPasswordService.getPublicKeys(user.id); - - // Verify master password - // UV service sets master key on success since it is stored in memory and can be lost on refresh - const verification = { - type: VerificationType.MasterPassword, - secret: masterPassword, - } as MasterPasswordVerification; - - const { masterKey } = await this.userVerificationService.verifyUserByMasterPassword( - verification, - user.id, - user.email, - ); - - const [newUserKey, newEncUserKey] = await this.keyService.makeUserKey(masterKey); - - if (newUserKey == null || newEncUserKey == null || newEncUserKey.encryptedString == null) { - this.logService.info("[Userkey rotation] User key could not be created. Aborting!"); - throw new Error("User key could not be created"); - } - - // New user key - const key = newEncUserKey.encryptedString; - - // Add master key hash - const masterPasswordHash = await this.keyService.hashMasterKey(masterPassword, masterKey); - - // Get original user key - // Note: We distribute the legacy key, but not all domains actually use it. If any of those - // domains break their legacy support it will break the migration process for legacy users. - const originalUserKey = await this.keyService.getUserKeyWithLegacySupport(user.id); - const isMasterKey = - (await firstValueFrom(this.keyService.userKey$(user.id))) != originalUserKey; - this.logService.info("[Userkey rotation] Is legacy user: " + isMasterKey); - - // Add re-encrypted data - const privateKey = await this.encryptPrivateKey(newUserKey, user.id); - if (privateKey == null) { - this.logService.info("[Userkey rotation] Private key could not be encrypted. Aborting!"); - throw new Error("Private key could not be encrypted"); - } - - // Create new request - const request = new UpdateKeyRequest(masterPasswordHash, key, privateKey); - - const rotatedCiphers = await this.cipherService.getRotatedData( - originalUserKey, - newUserKey, - user.id, - ); - if (rotatedCiphers != null) { - request.ciphers = rotatedCiphers; - } - - const rotatedFolders = await this.folderService.getRotatedData( - originalUserKey, - newUserKey, - user.id, - ); - if (rotatedFolders != null) { - request.folders = rotatedFolders; - } - - const rotatedSends = await this.sendService.getRotatedData( - originalUserKey, - newUserKey, - user.id, - ); - if (rotatedSends != null) { - request.sends = rotatedSends; - } - - const trustedUserPublicKeys = emergencyAccessGrantees.map((d) => d.publicKey); - const rotatedEmergencyAccessKeys = await this.emergencyAccessService.getRotatedData( - newUserKey, - trustedUserPublicKeys, - user.id, - ); - if (rotatedEmergencyAccessKeys != null) { - request.emergencyAccessKeys = rotatedEmergencyAccessKeys; - } - - const trustedOrgPublicKeys = orgs.map((d) => d.publicKey); - // Note: Reset password keys request model has user verification - // properties, but the rotation endpoint uses its own MP hash. - const rotatedResetPasswordKeys = await this.resetPasswordService.getRotatedData( - originalUserKey, - trustedOrgPublicKeys, - user.id, - ); - if (rotatedResetPasswordKeys != null) { - request.resetPasswordKeys = rotatedResetPasswordKeys; - } - - const rotatedWebauthnKeys = await this.webauthnLoginAdminService.getRotatedData( - originalUserKey, - newUserKey, - user.id, - ); - if (rotatedWebauthnKeys != null) { - request.webauthnKeys = rotatedWebauthnKeys; - } - - this.logService.info("[Userkey rotation] Posting user key rotation request to server"); - await this.apiService.postUserKeyUpdate(request); - this.logService.info("[Userkey rotation] Userkey rotation request posted to server"); - - // TODO PM-2199: Add device trust rotation support to the user key rotation endpoint - this.logService.info("[Userkey rotation] Rotating device trust..."); - await this.deviceTrustService.rotateDevicesTrust(user.id, newUserKey, masterPasswordHash); - this.logService.info("[Userkey rotation] Device trust rotation completed"); - await this.vaultTimeoutService.logOut(); - } - - private async encryptPrivateKey( - newUserKey: UserKey, - userId: UserId, - ): Promise<EncryptedString | undefined> { - const privateKey = await firstValueFrom( - this.keyService.userPrivateKeyWithLegacySupport$(userId), - ); - if (privateKey == null) { - throw new Error("No private key found for user key rotation"); - } - return (await this.encryptService.wrapDecapsulationKey(privateKey, newUserKey)).encryptedString; - } } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index b78f5a1deec..f64590b9e66 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -38,7 +38,6 @@ export enum FeatureFlag { /* Key Management */ PrivateKeyRegeneration = "pm-12241-private-key-regeneration", - UserKeyRotationV2 = "userkey-rotation-v2", PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service", UseSDKForDecryption = "use-sdk-for-decryption", PM17987_BlockType0 = "pm-17987-block-type-0", @@ -116,7 +115,6 @@ export const DefaultFeatureFlagValue = { /* Key Management */ [FeatureFlag.PrivateKeyRegeneration]: FALSE, - [FeatureFlag.UserKeyRotationV2]: FALSE, [FeatureFlag.PM4154_BulkEncryptionService]: FALSE, [FeatureFlag.UseSDKForDecryption]: FALSE, [FeatureFlag.PM17987_BlockType0]: FALSE, From dcd6f7ada86a799a865b16c1eef436cb8d746b1e Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 3 Jun 2025 15:04:46 -0700 Subject: [PATCH 235/245] [PM-19826] - browser - clear history after creating new cipher (#14894) * browser - clear history after creating new cipher * fix tests --- .../components/vault-v2/add-edit/add-edit-v2.component.spec.ts | 3 ++- .../components/vault-v2/add-edit/add-edit-v2.component.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts index 6974e6f7359..be772fa6ee5 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts @@ -51,6 +51,7 @@ describe("AddEditV2Component", () => { const disable = jest.fn(); const navigate = jest.fn(); const back = jest.fn().mockResolvedValue(null); + const setHistory = jest.fn(); const collect = jest.fn().mockResolvedValue(null); beforeEach(async () => { @@ -70,7 +71,7 @@ describe("AddEditV2Component", () => { providers: [ { provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() }, { provide: ConfigService, useValue: mock<ConfigService>() }, - { provide: PopupRouterCacheService, useValue: { back } }, + { provide: PopupRouterCacheService, useValue: { back, setHistory } }, { provide: PopupCloseWarningService, useValue: { disable } }, { provide: Router, useValue: { navigate } }, { provide: ActivatedRoute, useValue: { queryParams: queryParams$ } }, diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts index a5a6a6f9922..83bced88821 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts @@ -265,6 +265,8 @@ export class AddEditV2Component implements OnInit { replaceUrl: true, queryParams: { cipherId: cipher.id }, }); + // Clear popup history so after closing/reopening, Back won’t return to the add-edit form + await this.popupRouterCacheService.setHistory([]); } } From 65f4ff69099dff5a342a3e26238978a125e10cf9 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Wed, 4 Jun 2025 08:07:44 -0500 Subject: [PATCH 236/245] [PM-21791] Nudge UI Bug Fixes (#15010) * remove margin bottom from empty vault nudge * update page title to vault options * show badge on import of vault settings * add margin between no items title and icon * add mock to test * add comment for destroying vault settings page * fix logic for manage/create collection * account for deleted ciphers when showing the import nudge * refactor name of vault import nudge --- .../vault-v2/vault-v2.component.html | 2 +- .../settings/vault-settings-v2.component.html | 14 +++- .../settings/vault-settings-v2.component.ts | 26 ++++++- .../services/custom-nudges-services/index.ts | 1 + .../vault-settings-import-nudge.service.ts | 74 +++++++++++++++++++ .../src/vault/services/nudges.service.spec.ts | 5 ++ .../src/vault/services/nudges.service.ts | 3 + .../src/no-items/no-items.component.html | 2 +- 8 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 libs/angular/src/vault/services/custom-nudges-services/vault-settings-import-nudge.service.ts diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index da7b1393590..ddd26b77425 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -46,7 +46,7 @@ [title]="'hasItemsVaultNudgeTitle' | i18n" (onDismiss)="dismissVaultNudgeSpotlight(NudgeType.HasVaultItems)" > - <ul class="tw-pl-4 tw-text-main" bitTypography="body2"> + <ul class="tw-pl-4 tw-text-main tw-mb-0" bitTypography="body2"> <li>{{ "hasItemsVaultNudgeBodyOne" | i18n }}</li> <li>{{ "hasItemsVaultNudgeBodyTwo" | i18n }}</li> <li>{{ "hasItemsVaultNudgeBodyThree" | i18n }}</li> diff --git a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html index 03dd1182fbb..4e16f58d7f8 100644 --- a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html +++ b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html @@ -1,5 +1,5 @@ <popup-page> - <popup-header slot="header" [pageTitle]="'vault' | i18n" showBackButton> + <popup-header slot="header" [pageTitle]="'settingsVaultOptions' | i18n" showBackButton> <ng-container slot="end"> <app-pop-out></app-pop-out> </ng-container> @@ -14,7 +14,17 @@ </bit-item> <bit-item> <button type="button" bit-item-content (click)="import()"> - {{ "importItems" | i18n }} + <div class="tw-flex tw-items-center tw-justify-center tw-gap-2"> + <p>{{ "importItems" | i18n }}</p> + <span + *ngIf="emptyVaultImportBadge$ | async" + bitBadge + variant="notification" + [attr.aria-label]="'nudgeBadgeAria' | i18n" + > + 1 + </span> + </div> <i slot="end" class="bwi bwi-popout" aria-hidden="true"></i> </button> </bit-item> diff --git a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts index 9efdc568997..6f7940a2827 100644 --- a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts @@ -1,11 +1,15 @@ import { CommonModule } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; +import { Component, OnDestroy, OnInit } from "@angular/core"; import { Router, RouterModule } from "@angular/router"; +import { firstValueFrom, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { ItemModule, ToastOptions, ToastService } from "@bitwarden/components"; +import { BadgeComponent, ItemModule, ToastOptions, ToastService } from "@bitwarden/components"; import { BrowserApi } from "../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; @@ -23,22 +27,38 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co PopupHeaderComponent, PopOutComponent, ItemModule, + BadgeComponent, ], }) -export class VaultSettingsV2Component implements OnInit { +export class VaultSettingsV2Component implements OnInit, OnDestroy { lastSync = "--"; + protected emptyVaultImportBadge$ = this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => + this.nudgeService.showNudgeBadge$(NudgeType.VaultSettingsImportNudge, userId), + ), + ); + constructor( private router: Router, private syncService: SyncService, private toastService: ToastService, private i18nService: I18nService, + private nudgeService: NudgesService, + private accountService: AccountService, ) {} async ngOnInit() { await this.setLastSync(); } + async ngOnDestroy(): Promise<void> { + // When a user navigates away from the page, dismiss the empty vault import nudge + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.nudgeService.dismissNudge(NudgeType.VaultSettingsImportNudge, userId); + } + async import() { await this.router.navigate(["/import"]); if (await BrowserApi.isPopupOpen()) { diff --git a/libs/angular/src/vault/services/custom-nudges-services/index.ts b/libs/angular/src/vault/services/custom-nudges-services/index.ts index e94b8bf71e5..10b6b45aa1d 100644 --- a/libs/angular/src/vault/services/custom-nudges-services/index.ts +++ b/libs/angular/src/vault/services/custom-nudges-services/index.ts @@ -3,4 +3,5 @@ export * from "./account-security-nudge.service"; export * from "./has-items-nudge.service"; export * from "./download-bitwarden-nudge.service"; export * from "./empty-vault-nudge.service"; +export * from "./vault-settings-import-nudge.service"; export * from "./new-item-nudge.service"; diff --git a/libs/angular/src/vault/services/custom-nudges-services/vault-settings-import-nudge.service.ts b/libs/angular/src/vault/services/custom-nudges-services/vault-settings-import-nudge.service.ts new file mode 100644 index 00000000000..2d86c76dff7 --- /dev/null +++ b/libs/angular/src/vault/services/custom-nudges-services/vault-settings-import-nudge.service.ts @@ -0,0 +1,74 @@ +import { inject, Injectable } from "@angular/core"; +import { combineLatest, Observable, of, switchMap } from "rxjs"; + +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports +import { CollectionService } from "@bitwarden/admin-console/common"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { UserId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; + +import { DefaultSingleNudgeService } from "../default-single-nudge.service"; +import { NudgeStatus, NudgeType } from "../nudges.service"; + +/** + * Custom Nudge Service for the vault settings import badge. + */ +@Injectable({ + providedIn: "root", +}) +export class VaultSettingsImportNudgeService extends DefaultSingleNudgeService { + cipherService = inject(CipherService); + organizationService = inject(OrganizationService); + collectionService = inject(CollectionService); + + nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable<NudgeStatus> { + return combineLatest([ + this.getNudgeStatus$(nudgeType, userId), + this.cipherService.cipherViews$(userId), + this.organizationService.organizations$(userId), + this.collectionService.decryptedCollections$, + ]).pipe( + switchMap(([nudgeStatus, ciphers, orgs, collections]) => { + const vaultHasMoreThanOneItem = (ciphers?.length ?? 0) > 1; + const { hasBadgeDismissed, hasSpotlightDismissed } = nudgeStatus; + + // When the user has no organizations, return the nudge status directly + if ((orgs?.length ?? 0) === 0) { + return hasBadgeDismissed || hasSpotlightDismissed + ? of(nudgeStatus) + : of({ + hasSpotlightDismissed: vaultHasMoreThanOneItem, + hasBadgeDismissed: vaultHasMoreThanOneItem, + }); + } + + const orgIds = new Set(orgs.map((org) => org.id)); + const canCreateCollections = orgs.some((org) => org.canCreateNewCollections); + const hasManageCollections = collections.some( + (c) => c.manage && orgIds.has(c.organizationId), + ); + + // When the user has dismissed the nudge or spotlight, return the nudge status directly + if (hasBadgeDismissed || hasSpotlightDismissed) { + return of(nudgeStatus); + } + + // When the user belongs to an organization and cannot create collections or manage collections, + // hide the nudge and spotlight + if (!hasManageCollections && !canCreateCollections) { + return of({ + hasSpotlightDismissed: true, + hasBadgeDismissed: true, + }); + } + + // Otherwise, return the nudge status based on the vault contents + return of({ + hasSpotlightDismissed: vaultHasMoreThanOneItem, + hasBadgeDismissed: vaultHasMoreThanOneItem, + }); + }), + ); + } +} diff --git a/libs/angular/src/vault/services/nudges.service.spec.ts b/libs/angular/src/vault/services/nudges.service.spec.ts index 30e2ada6007..db1091c0956 100644 --- a/libs/angular/src/vault/services/nudges.service.spec.ts +++ b/libs/angular/src/vault/services/nudges.service.spec.ts @@ -20,6 +20,7 @@ import { HasItemsNudgeService, EmptyVaultNudgeService, DownloadBitwardenNudgeService, + VaultSettingsImportNudgeService, } from "./custom-nudges-services"; import { DefaultSingleNudgeService } from "./default-single-nudge.service"; import { NudgesService, NudgeType } from "./nudges.service"; @@ -64,6 +65,10 @@ describe("Vault Nudges Service", () => { provide: EmptyVaultNudgeService, useValue: mock<EmptyVaultNudgeService>(), }, + { + provide: VaultSettingsImportNudgeService, + useValue: mock<VaultSettingsImportNudgeService>(), + }, { provide: ApiService, useValue: mock<ApiService>(), diff --git a/libs/angular/src/vault/services/nudges.service.ts b/libs/angular/src/vault/services/nudges.service.ts index 9ba46a3bb6d..25b111d8883 100644 --- a/libs/angular/src/vault/services/nudges.service.ts +++ b/libs/angular/src/vault/services/nudges.service.ts @@ -13,6 +13,7 @@ import { DownloadBitwardenNudgeService, NewItemNudgeService, AccountSecurityNudgeService, + VaultSettingsImportNudgeService, } from "./custom-nudges-services"; import { DefaultSingleNudgeService, SingleNudgeService } from "./default-single-nudge.service"; @@ -31,6 +32,7 @@ export enum NudgeType { * Add future nudges here */ EmptyVaultNudge = "empty-vault-nudge", + VaultSettingsImportNudge = "vault-settings-import-nudge", HasVaultItems = "has-vault-items", AutofillNudge = "autofill-nudge", AccountSecurity = "account-security", @@ -64,6 +66,7 @@ export class NudgesService { private customNudgeServices: Partial<Record<NudgeType, SingleNudgeService>> = { [NudgeType.HasVaultItems]: inject(HasItemsNudgeService), [NudgeType.EmptyVaultNudge]: inject(EmptyVaultNudgeService), + [NudgeType.VaultSettingsImportNudge]: inject(VaultSettingsImportNudgeService), [NudgeType.AccountSecurity]: inject(AccountSecurityNudgeService), [NudgeType.AutofillNudge]: inject(AutofillNudgeService), [NudgeType.DownloadBitwarden]: inject(DownloadBitwardenNudgeService), diff --git a/libs/components/src/no-items/no-items.component.html b/libs/components/src/no-items/no-items.component.html index bbc78cec3cb..ae2416d7a7b 100644 --- a/libs/components/src/no-items/no-items.component.html +++ b/libs/components/src/no-items/no-items.component.html @@ -1,7 +1,7 @@ <div class="tw-mx-auto tw-flex tw-flex-col tw-items-center tw-justify-center tw-pt-6"> <div class="tw-max-w-sm tw-flex tw-flex-col tw-items-center"> <bit-icon [icon]="icon" aria-hidden="true"></bit-icon> - <h3 class="tw-font-semibold tw-text-center"> + <h3 class="tw-font-semibold tw-text-center tw-mt-4"> <ng-content select="[slot=title]"></ng-content> </h3> <p class="tw-text-center"> From 1bd77fec7a37fd4ce20ad8bedab7ccb6f38e3962 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Wed, 4 Jun 2025 08:09:14 -0500 Subject: [PATCH 237/245] account for deleted ciphers for empty vault nudge (#15014) --- .../custom-nudges-services/empty-vault-nudge.service.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts b/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts index d579f8b1bb2..9763c202993 100644 --- a/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts +++ b/libs/angular/src/vault/services/custom-nudges-services/empty-vault-nudge.service.ts @@ -30,10 +30,7 @@ export class EmptyVaultNudgeService extends DefaultSingleNudgeService { this.collectionService.decryptedCollections$, ]).pipe( switchMap(([nudgeStatus, ciphers, orgs, collections]) => { - const filteredCiphers = ciphers?.filter((cipher) => { - return cipher.deletedDate == null; - }); - const vaultHasContents = !(filteredCiphers == null || filteredCiphers.length === 0); + const vaultHasContents = !(ciphers == null || ciphers.length === 0); if (orgs == null || orgs.length === 0) { return nudgeStatus.hasBadgeDismissed || nudgeStatus.hasSpotlightDismissed ? of(nudgeStatus) From 8f74eaea1c406393cfafb5edd6027cc1a1b1dc04 Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Wed, 4 Jun 2025 15:22:37 +0200 Subject: [PATCH 238/245] Remove standalone true from auth (#15035) Remove standalone: true from every instance since it's the default as of Angular 19. --- .../popup/account-switching/account-switcher.component.ts | 1 - .../src/auth/popup/account-switching/account.component.ts | 1 - .../auth/popup/account-switching/current-account.component.ts | 1 - apps/browser/src/auth/popup/components/set-pin.component.ts | 1 - .../extension-anon-layout-wrapper.component.ts | 1 - .../auth/popup/settings/account-security.component.spec.ts | 1 - .../src/auth/popup/settings/account-security.component.ts | 1 - .../src/auth/popup/settings/await-desktop-dialog.component.ts | 1 - apps/desktop/src/auth/components/set-pin.component.ts | 1 - apps/desktop/src/auth/delete-account.component.ts | 1 - .../emergency-access/accept/accept-emergency.component.ts | 1 - apps/web/src/app/auth/settings/account/account.component.ts | 1 - .../auth/settings/account/change-avatar-dialog.component.ts | 1 - .../src/app/auth/settings/account/change-email.component.ts | 1 - .../src/app/auth/settings/account/danger-zone.component.ts | 1 - .../auth/settings/account/deauthorize-sessions.component.ts | 1 - .../auth/settings/account/delete-account-dialog.component.ts | 1 - apps/web/src/app/auth/settings/account/profile.component.ts | 1 - .../app/auth/settings/account/selectable-avatar.component.ts | 1 - .../account/set-account-verify-devices-dialog.component.ts | 1 - .../emergency-access/view/emergency-view-dialog.component.ts | 1 - apps/web/src/app/auth/settings/security/api-key.component.ts | 1 - .../app/auth/settings/security/device-management.component.ts | 1 - .../security/password-settings/password-settings.component.ts | 1 - .../src/app/auth/settings/security/security-keys.component.ts | 1 - apps/web/src/app/auth/settings/security/security.component.ts | 1 - .../auth/settings/two-factor/two-factor-recovery.component.ts | 1 - .../two-factor/two-factor-setup-authenticator.component.ts | 1 - .../settings/two-factor/two-factor-setup-duo.component.ts | 1 - .../settings/two-factor/two-factor-setup-email.component.ts | 1 - .../two-factor/two-factor-setup-method-base.component.ts | 4 +--- .../two-factor/two-factor-setup-webauthn.component.ts | 1 - .../settings/two-factor/two-factor-setup-yubikey.component.ts | 1 - .../auth/settings/two-factor/two-factor-setup.component.ts | 1 - .../auth/settings/two-factor/two-factor-verify.component.ts | 1 - apps/web/src/app/auth/settings/verify-email.component.ts | 1 - .../src/auth/components/authentication-timeout.component.ts | 1 - .../src/angular/anon-layout/anon-layout-wrapper.component.ts | 1 - libs/auth/src/angular/anon-layout/anon-layout.component.ts | 1 - .../src/angular/change-password/change-password.component.ts | 1 - .../fingerprint-dialog/fingerprint-dialog.component.ts | 1 - .../src/angular/input-password/input-password.component.ts | 1 - .../src/angular/login-approval/login-approval.component.ts | 1 - .../login-decryption-options.component.ts | 1 - .../login-via-auth-request.component.ts | 1 - .../src/angular/login/login-secondary-content.component.ts | 1 - libs/auth/src/angular/login/login.component.ts | 1 - .../new-device-verification.component.ts | 1 - .../angular/password-callout/password-callout.component.ts | 1 - .../auth/src/angular/password-hint/password-hint.component.ts | 1 - .../registration-env-selector.component.ts | 1 - .../registration-finish/registration-finish.component.ts | 1 - .../registration-link-expired.component.ts | 1 - .../registration-start-secondary.component.ts | 1 - .../registration-start/registration-start.component.ts | 1 - .../self-hosted-env-config-dialog.component.ts | 1 - .../angular/set-password-jit/set-password-jit.component.ts | 1 - libs/auth/src/angular/sso/sso.component.ts | 1 - .../two-factor-auth-authenticator.component.ts | 1 - .../two-factor-auth-duo/two-factor-auth-duo.component.ts | 1 - .../two-factor-auth-email/two-factor-auth-email.component.ts | 1 - .../two-factor-auth-webauthn.component.ts | 1 - .../child-components/two-factor-auth-yubikey.component.ts | 1 - .../src/angular/two-factor-auth/two-factor-auth.component.ts | 1 - .../angular/two-factor-auth/two-factor-options.component.ts | 1 - .../user-verification/user-verification-dialog.component.ts | 1 - .../user-verification-form-input.component.ts | 1 - .../vault-timeout-input/vault-timeout-input.component.ts | 1 - 68 files changed, 1 insertion(+), 70 deletions(-) diff --git a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts index 78bee121afb..3c94fbeef70 100644 --- a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts +++ b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts @@ -34,7 +34,6 @@ import { CurrentAccountComponent } from "./current-account.component"; import { AccountSwitcherService } from "./services/account-switcher.service"; @Component({ - standalone: true, templateUrl: "account-switcher.component.html", imports: [ CommonModule, diff --git a/apps/browser/src/auth/popup/account-switching/account.component.ts b/apps/browser/src/auth/popup/account-switching/account.component.ts index dad74977d34..cdd2656fdc1 100644 --- a/apps/browser/src/auth/popup/account-switching/account.component.ts +++ b/apps/browser/src/auth/popup/account-switching/account.component.ts @@ -13,7 +13,6 @@ import { BiometricsService } from "@bitwarden/key-management"; import { AccountSwitcherService, AvailableAccount } from "./services/account-switcher.service"; @Component({ - standalone: true, selector: "auth-account", templateUrl: "account.component.html", imports: [CommonModule, JslibModule, AvatarModule, ItemModule], diff --git a/apps/browser/src/auth/popup/account-switching/current-account.component.ts b/apps/browser/src/auth/popup/account-switching/current-account.component.ts index ea41a627848..63e8481621a 100644 --- a/apps/browser/src/auth/popup/account-switching/current-account.component.ts +++ b/apps/browser/src/auth/popup/account-switching/current-account.component.ts @@ -24,7 +24,6 @@ export type CurrentAccount = { @Component({ selector: "app-current-account", templateUrl: "current-account.component.html", - standalone: true, imports: [CommonModule, JslibModule, AvatarModule, RouterModule], }) export class CurrentAccountComponent { diff --git a/apps/browser/src/auth/popup/components/set-pin.component.ts b/apps/browser/src/auth/popup/components/set-pin.component.ts index d79f9eeca89..a9e8e1b122f 100644 --- a/apps/browser/src/auth/popup/components/set-pin.component.ts +++ b/apps/browser/src/auth/popup/components/set-pin.component.ts @@ -14,7 +14,6 @@ import { } from "@bitwarden/components"; @Component({ - standalone: true, templateUrl: "set-pin.component.html", imports: [ DialogModule, diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts index d6cccf31bb4..b335155d355 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts @@ -31,7 +31,6 @@ export interface ExtensionAnonLayoutWrapperData extends AnonLayoutWrapperData { } @Component({ - standalone: true, templateUrl: "extension-anon-layout-wrapper.component.html", imports: [ AnonLayoutComponent, diff --git a/apps/browser/src/auth/popup/settings/account-security.component.spec.ts b/apps/browser/src/auth/popup/settings/account-security.component.spec.ts index 56b18068778..fe9c8c1bf06 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.spec.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.spec.ts @@ -40,7 +40,6 @@ import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popu import { AccountSecurityComponent } from "./account-security.component"; @Component({ - standalone: true, selector: "app-pop-out", template: ` <ng-content></ng-content>`, }) diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 26a805b3624..af716ee2301 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -79,7 +79,6 @@ import { AwaitDesktopDialogComponent } from "./await-desktop-dialog.component"; @Component({ templateUrl: "account-security.component.html", - standalone: true, imports: [ CardComponent, CheckboxModule, diff --git a/apps/browser/src/auth/popup/settings/await-desktop-dialog.component.ts b/apps/browser/src/auth/popup/settings/await-desktop-dialog.component.ts index f7c4351dec3..11bb9683bb9 100644 --- a/apps/browser/src/auth/popup/settings/await-desktop-dialog.component.ts +++ b/apps/browser/src/auth/popup/settings/await-desktop-dialog.component.ts @@ -5,7 +5,6 @@ import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components @Component({ templateUrl: "await-desktop-dialog.component.html", - standalone: true, imports: [JslibModule, ButtonModule, DialogModule], }) export class AwaitDesktopDialogComponent { diff --git a/apps/desktop/src/auth/components/set-pin.component.ts b/apps/desktop/src/auth/components/set-pin.component.ts index a5221442a3f..93e1ea0d25c 100644 --- a/apps/desktop/src/auth/components/set-pin.component.ts +++ b/apps/desktop/src/auth/components/set-pin.component.ts @@ -13,7 +13,6 @@ import { IconButtonModule, } from "@bitwarden/components"; @Component({ - standalone: true, templateUrl: "set-pin.component.html", imports: [ DialogModule, diff --git a/apps/desktop/src/auth/delete-account.component.ts b/apps/desktop/src/auth/delete-account.component.ts index cfa47ef33e5..b6c6650375d 100644 --- a/apps/desktop/src/auth/delete-account.component.ts +++ b/apps/desktop/src/auth/delete-account.component.ts @@ -22,7 +22,6 @@ import { UserVerificationComponent } from "../app/components/user-verification.c @Component({ selector: "app-delete-account", - standalone: true, templateUrl: "delete-account.component.html", imports: [ JslibModule, diff --git a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts index b3f635aee92..e1b7329504c 100644 --- a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts +++ b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts @@ -13,7 +13,6 @@ import { EmergencyAccessModule } from "../emergency-access.module"; import { EmergencyAccessService } from "../services/emergency-access.service"; @Component({ - standalone: true, imports: [SharedModule, EmergencyAccessModule], templateUrl: "accept-emergency.component.html", }) diff --git a/apps/web/src/app/auth/settings/account/account.component.ts b/apps/web/src/app/auth/settings/account/account.component.ts index c06df56e386..921db19bc49 100644 --- a/apps/web/src/app/auth/settings/account/account.component.ts +++ b/apps/web/src/app/auth/settings/account/account.component.ts @@ -21,7 +21,6 @@ import { SetAccountVerifyDevicesDialogComponent } from "./set-account-verify-dev @Component({ templateUrl: "account.component.html", - standalone: true, imports: [ SharedModule, HeaderModule, diff --git a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts index 80fdb20954f..6bb785fb8f5 100644 --- a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts @@ -35,7 +35,6 @@ type ChangeAvatarDialogData = { @Component({ templateUrl: "change-avatar-dialog.component.html", encapsulation: ViewEncapsulation.None, - standalone: true, imports: [SharedModule, SelectableAvatarComponent], }) export class ChangeAvatarDialogComponent implements OnInit, OnDestroy { diff --git a/apps/web/src/app/auth/settings/account/change-email.component.ts b/apps/web/src/app/auth/settings/account/change-email.component.ts index 98f704d6044..a55846a5c0f 100644 --- a/apps/web/src/app/auth/settings/account/change-email.component.ts +++ b/apps/web/src/app/auth/settings/account/change-email.component.ts @@ -19,7 +19,6 @@ import { SharedModule } from "../../../shared"; @Component({ selector: "app-change-email", templateUrl: "change-email.component.html", - standalone: true, imports: [SharedModule], }) export class ChangeEmailComponent implements OnInit { diff --git a/apps/web/src/app/auth/settings/account/danger-zone.component.ts b/apps/web/src/app/auth/settings/account/danger-zone.component.ts index e07b6e6b8db..05fd22d087d 100644 --- a/apps/web/src/app/auth/settings/account/danger-zone.component.ts +++ b/apps/web/src/app/auth/settings/account/danger-zone.component.ts @@ -12,7 +12,6 @@ import { I18nPipe } from "@bitwarden/ui-common"; @Component({ selector: "app-danger-zone", templateUrl: "danger-zone.component.html", - standalone: true, imports: [CommonModule, TypographyModule, I18nPipe], }) export class DangerZoneComponent {} diff --git a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts index a48e968ab3e..f75320e8335 100644 --- a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts +++ b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts @@ -14,7 +14,6 @@ import { SharedModule } from "../../../shared"; @Component({ templateUrl: "deauthorize-sessions.component.html", - standalone: true, imports: [SharedModule, UserVerificationFormInputComponent], }) export class DeauthorizeSessionsComponent { diff --git a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts index 0fc2276b779..7e8f169994f 100644 --- a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts @@ -14,7 +14,6 @@ import { SharedModule } from "../../../shared"; @Component({ templateUrl: "delete-account-dialog.component.html", - standalone: true, imports: [SharedModule, UserVerificationFormInputComponent], }) export class DeleteAccountDialogComponent { diff --git a/apps/web/src/app/auth/settings/account/profile.component.ts b/apps/web/src/app/auth/settings/account/profile.component.ts index a33efd742aa..a0572b846db 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.ts +++ b/apps/web/src/app/auth/settings/account/profile.component.ts @@ -23,7 +23,6 @@ import { ChangeAvatarDialogComponent } from "./change-avatar-dialog.component"; @Component({ selector: "app-profile", templateUrl: "profile.component.html", - standalone: true, imports: [SharedModule, DynamicAvatarComponent, AccountFingerprintComponent], }) export class ProfileComponent implements OnInit, OnDestroy { diff --git a/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts index a53e3990090..630c0e949ad 100644 --- a/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts +++ b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts @@ -27,7 +27,6 @@ import { AvatarModule } from "@bitwarden/components"; > </bit-avatar> </span>`, - standalone: true, imports: [NgClass, AvatarModule], }) export class SelectableAvatarComponent { diff --git a/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts b/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts index 69240426249..63a26f08eee 100644 --- a/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts @@ -29,7 +29,6 @@ import { @Component({ templateUrl: "./set-account-verify-devices-dialog.component.html", - standalone: true, imports: [ CommonModule, ReactiveFormsModule, diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts index 0022da7f3a9..67612e5dcd3 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts @@ -38,7 +38,6 @@ class PremiumUpgradePromptNoop implements PremiumUpgradePromptService { @Component({ selector: "app-emergency-view-dialog", templateUrl: "emergency-view-dialog.component.html", - standalone: true, imports: [ButtonModule, CipherViewComponent, DialogModule, CommonModule, JslibModule], providers: [ { provide: ViewPasswordHistoryService, useClass: VaultViewPasswordHistoryService }, diff --git a/apps/web/src/app/auth/settings/security/api-key.component.ts b/apps/web/src/app/auth/settings/security/api-key.component.ts index 5e61b4b4584..82d1010f020 100644 --- a/apps/web/src/app/auth/settings/security/api-key.component.ts +++ b/apps/web/src/app/auth/settings/security/api-key.component.ts @@ -25,7 +25,6 @@ export type ApiKeyDialogData = { }; @Component({ templateUrl: "api-key.component.html", - standalone: true, imports: [SharedModule, UserVerificationFormInputComponent], }) export class ApiKeyComponent { diff --git a/apps/web/src/app/auth/settings/security/device-management.component.ts b/apps/web/src/app/auth/settings/security/device-management.component.ts index 631ab02db7d..c831d26ea16 100644 --- a/apps/web/src/app/auth/settings/security/device-management.component.ts +++ b/apps/web/src/app/auth/settings/security/device-management.component.ts @@ -46,7 +46,6 @@ interface DeviceTableData { @Component({ selector: "app-device-management", templateUrl: "./device-management.component.html", - standalone: true, imports: [CommonModule, SharedModule, TableModule, PopoverModule], }) export class DeviceManagementComponent { diff --git a/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts index ee30543fba2..d94df18136e 100644 --- a/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts +++ b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts @@ -10,7 +10,6 @@ import { I18nPipe } from "@bitwarden/ui-common"; import { WebauthnLoginSettingsModule } from "../../webauthn-login-settings"; @Component({ - standalone: true, selector: "app-password-settings", templateUrl: "password-settings.component.html", imports: [CalloutModule, ChangePasswordComponent, I18nPipe, WebauthnLoginSettingsModule], diff --git a/apps/web/src/app/auth/settings/security/security-keys.component.ts b/apps/web/src/app/auth/settings/security/security-keys.component.ts index 6d33193cdde..c77109936ee 100644 --- a/apps/web/src/app/auth/settings/security/security-keys.component.ts +++ b/apps/web/src/app/auth/settings/security/security-keys.component.ts @@ -15,7 +15,6 @@ import { ChangeKdfModule } from "./change-kdf/change-kdf.module"; @Component({ templateUrl: "security-keys.component.html", - standalone: true, imports: [SharedModule, ChangeKdfModule], }) export class SecurityKeysComponent implements OnInit { diff --git a/apps/web/src/app/auth/settings/security/security.component.ts b/apps/web/src/app/auth/settings/security/security.component.ts index 95733d693e2..2240371637d 100644 --- a/apps/web/src/app/auth/settings/security/security.component.ts +++ b/apps/web/src/app/auth/settings/security/security.component.ts @@ -9,7 +9,6 @@ import { SharedModule } from "../../../shared"; @Component({ templateUrl: "security.component.html", - standalone: true, imports: [SharedModule, HeaderModule], }) export class SecurityComponent implements OnInit { diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.ts index 75a97661311..37d94bfae0e 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.ts @@ -18,7 +18,6 @@ import { I18nPipe } from "@bitwarden/ui-common"; @Component({ selector: "app-two-factor-recovery", templateUrl: "two-factor-recovery.component.html", - standalone: true, imports: [CommonModule, DialogModule, ButtonModule, TypographyModule, I18nPipe], }) export class TwoFactorRecoveryComponent { diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts index 030805ed3eb..698e0911b04 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts @@ -56,7 +56,6 @@ declare global { @Component({ selector: "app-two-factor-setup-authenticator", templateUrl: "two-factor-setup-authenticator.component.html", - standalone: true, imports: [ CommonModule, ReactiveFormsModule, diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts index bada3301a97..0efd0c79b4e 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts @@ -33,7 +33,6 @@ import { TwoFactorSetupMethodBaseComponent } from "./two-factor-setup-method-bas @Component({ selector: "app-two-factor-setup-duo", templateUrl: "two-factor-setup-duo.component.html", - standalone: true, imports: [ CommonModule, DialogModule, diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts index c5692c3f080..544f3850ea6 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts @@ -36,7 +36,6 @@ import { TwoFactorSetupMethodBaseComponent } from "./two-factor-setup-method-bas @Component({ selector: "app-two-factor-setup-email", templateUrl: "two-factor-setup-email.component.html", - standalone: true, imports: [ AsyncActionsModule, ButtonModule, diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts index 0654ad126e2..7569577e781 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts @@ -15,9 +15,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; /** * Base class for two-factor setup components (ex: email, yubikey, webauthn, duo). */ -@Directive({ - standalone: true, -}) +@Directive({}) export abstract class TwoFactorSetupMethodBaseComponent { @Output() onUpdated = new EventEmitter<boolean>(); diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts index 44acc6dabca..66cd3596063 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts @@ -46,7 +46,6 @@ interface Key { @Component({ selector: "app-two-factor-setup-webauthn", templateUrl: "two-factor-setup-webauthn.component.html", - standalone: true, imports: [ AsyncActionsModule, ButtonModule, diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.ts index 92236fe21ab..0b85d219928 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.ts @@ -47,7 +47,6 @@ interface Key { @Component({ selector: "app-two-factor-setup-yubikey", templateUrl: "two-factor-setup-yubikey.component.html", - standalone: true, imports: [ CommonModule, ReactiveFormsModule, diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts index 88c9eea2cb0..7259c3f0fe8 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts @@ -47,7 +47,6 @@ import { TwoFactorVerifyComponent } from "./two-factor-verify.component"; @Component({ selector: "app-two-factor-setup", templateUrl: "two-factor-setup.component.html", - standalone: true, imports: [ItemModule, LooseComponentsModule, SharedModule], }) export class TwoFactorSetupComponent implements OnInit, OnDestroy { diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts index a153a9ec56a..07939db7eff 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts @@ -31,7 +31,6 @@ type TwoFactorVerifyDialogData = { @Component({ selector: "app-two-factor-verify", templateUrl: "two-factor-verify.component.html", - standalone: true, imports: [ AsyncActionsModule, ButtonModule, diff --git a/apps/web/src/app/auth/settings/verify-email.component.ts b/apps/web/src/app/auth/settings/verify-email.component.ts index 001b791b748..7088dae8d0f 100644 --- a/apps/web/src/app/auth/settings/verify-email.component.ts +++ b/apps/web/src/app/auth/settings/verify-email.component.ts @@ -17,7 +17,6 @@ import { } from "@bitwarden/components"; @Component({ - standalone: true, selector: "app-verify-email", templateUrl: "verify-email.component.html", imports: [AsyncActionsModule, BannerModule, ButtonModule, CommonModule, JslibModule, LinkModule], diff --git a/libs/angular/src/auth/components/authentication-timeout.component.ts b/libs/angular/src/auth/components/authentication-timeout.component.ts index 1a5d398a291..940798de9e7 100644 --- a/libs/angular/src/auth/components/authentication-timeout.component.ts +++ b/libs/angular/src/auth/components/authentication-timeout.component.ts @@ -11,7 +11,6 @@ import { ButtonModule } from "@bitwarden/components"; */ @Component({ selector: "app-authentication-timeout", - standalone: true, imports: [CommonModule, JslibModule, ButtonModule, RouterModule], template: ` <p class="tw-text-center"> diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts index 17f5ec8e3c6..69f1dd1be63 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.component.ts @@ -44,7 +44,6 @@ export interface AnonLayoutWrapperData { } @Component({ - standalone: true, templateUrl: "anon-layout-wrapper.component.html", imports: [AnonLayoutComponent, RouterModule], }) diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.ts b/libs/auth/src/angular/anon-layout/anon-layout.component.ts index 1ca4ccd2432..1a20dd6fb52 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout.component.ts @@ -21,7 +21,6 @@ import { TypographyModule } from "../../../../components/src/typography"; import { BitwardenLogo, BitwardenShield } from "../icons"; @Component({ - standalone: true, selector: "auth-anon-layout", templateUrl: "./anon-layout.component.html", imports: [IconModule, CommonModule, TypographyModule, SharedModule, RouterModule], diff --git a/libs/auth/src/angular/change-password/change-password.component.ts b/libs/auth/src/angular/change-password/change-password.component.ts index 86b7c884e92..a3f2839d1fb 100644 --- a/libs/auth/src/angular/change-password/change-password.component.ts +++ b/libs/auth/src/angular/change-password/change-password.component.ts @@ -22,7 +22,6 @@ import { PasswordInputResult } from "../input-password/password-input-result"; import { ChangePasswordService } from "./change-password.service.abstraction"; @Component({ - standalone: true, selector: "auth-change-password", templateUrl: "change-password.component.html", imports: [InputPasswordComponent, I18nPipe], diff --git a/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts b/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts index 17d7b343db9..1769a57319c 100644 --- a/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts +++ b/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts @@ -11,7 +11,6 @@ export type FingerprintDialogData = { @Component({ templateUrl: "fingerprint-dialog.component.html", - standalone: true, imports: [JslibModule, ButtonModule, DialogModule], }) export class FingerprintDialogComponent { diff --git a/libs/auth/src/angular/input-password/input-password.component.ts b/libs/auth/src/angular/input-password/input-password.component.ts index 8b0b6e4c159..49fe03ff855 100644 --- a/libs/auth/src/angular/input-password/input-password.component.ts +++ b/libs/auth/src/angular/input-password/input-password.component.ts @@ -82,7 +82,6 @@ interface InputPasswordForm { } @Component({ - standalone: true, selector: "auth-input-password", templateUrl: "./input-password.component.html", imports: [ diff --git a/libs/auth/src/angular/login-approval/login-approval.component.ts b/libs/auth/src/angular/login-approval/login-approval.component.ts index 784bd93002e..e54e15f11f3 100644 --- a/libs/auth/src/angular/login-approval/login-approval.component.ts +++ b/libs/auth/src/angular/login-approval/login-approval.component.ts @@ -40,7 +40,6 @@ export interface LoginApprovalDialogParams { @Component({ selector: "login-approval", templateUrl: "login-approval.component.html", - standalone: true, imports: [CommonModule, AsyncActionsModule, ButtonModule, DialogModule, JslibModule], }) export class LoginApprovalComponent implements OnInit, OnDestroy { diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts index c49e54f8c19..0de51d83ac8 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts @@ -51,7 +51,6 @@ enum State { } @Component({ - standalone: true, templateUrl: "./login-decryption-options.component.html", imports: [ AsyncActionsModule, diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts index f23d49f0015..9912c45e9d2 100644 --- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts +++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts @@ -57,7 +57,6 @@ const matchOptions: IsActiveMatchOptions = { }; @Component({ - standalone: true, templateUrl: "./login-via-auth-request.component.html", imports: [ButtonModule, CommonModule, JslibModule, LinkModule, RouterModule], providers: [{ provide: LoginViaAuthRequestCacheService }], diff --git a/libs/auth/src/angular/login/login-secondary-content.component.ts b/libs/auth/src/angular/login/login-secondary-content.component.ts index fba5e70d93c..9cd4cfd2502 100644 --- a/libs/auth/src/angular/login/login-secondary-content.component.ts +++ b/libs/auth/src/angular/login/login-secondary-content.component.ts @@ -9,7 +9,6 @@ import { DefaultServerSettingsService } from "@bitwarden/common/platform/service import { LinkModule } from "@bitwarden/components"; @Component({ - standalone: true, imports: [CommonModule, JslibModule, LinkModule, RouterModule], template: ` <div class="tw-text-center" *ngIf="!(isUserRegistrationDisabled$ | async)"> diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index 425260ec2e0..aaff86224ff 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -56,7 +56,6 @@ export enum LoginUiState { } @Component({ - standalone: true, templateUrl: "./login.component.html", imports: [ AsyncActionsModule, diff --git a/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts b/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts index 5d3ecc1751d..0d7ae4f0356 100644 --- a/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts +++ b/libs/auth/src/angular/new-device-verification/new-device-verification.component.ts @@ -25,7 +25,6 @@ import { LoginStrategyServiceAbstraction } from "../../common/abstractions/login * Component for verifying a new device via a one-time password (OTP). */ @Component({ - standalone: true, selector: "app-new-device-verification", templateUrl: "./new-device-verification.component.html", imports: [ diff --git a/libs/auth/src/angular/password-callout/password-callout.component.ts b/libs/auth/src/angular/password-callout/password-callout.component.ts index 03fba52336f..7a28700f109 100644 --- a/libs/auth/src/angular/password-callout/password-callout.component.ts +++ b/libs/auth/src/angular/password-callout/password-callout.component.ts @@ -13,7 +13,6 @@ import { CalloutModule } from "@bitwarden/components"; @Component({ selector: "auth-password-callout", templateUrl: "password-callout.component.html", - standalone: true, imports: [CommonModule, JslibModule, CalloutModule], }) export class PasswordCalloutComponent { diff --git a/libs/auth/src/angular/password-hint/password-hint.component.ts b/libs/auth/src/angular/password-hint/password-hint.component.ts index 99eb06293e0..3189bf8f187 100644 --- a/libs/auth/src/angular/password-hint/password-hint.component.ts +++ b/libs/auth/src/angular/password-hint/password-hint.component.ts @@ -23,7 +23,6 @@ import { } from "@bitwarden/components"; @Component({ - standalone: true, templateUrl: "./password-hint.component.html", imports: [ AsyncActionsModule, diff --git a/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts b/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts index 8aac5f73ffe..93ddd00fdd6 100644 --- a/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts +++ b/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.ts @@ -26,7 +26,6 @@ import { SelfHostedEnvConfigDialogComponent } from "../../self-hosted-env-config * Outputs the selected region to the parent component so it can respond as necessary. */ @Component({ - standalone: true, selector: "auth-registration-env-selector", templateUrl: "registration-env-selector.component.html", imports: [CommonModule, JslibModule, ReactiveFormsModule, FormFieldModule, SelectModule], diff --git a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts index 353e3772c41..c3a09a897e5 100644 --- a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts +++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts @@ -33,7 +33,6 @@ import { PasswordInputResult } from "../../input-password/password-input-result" import { RegistrationFinishService } from "./registration-finish.service"; @Component({ - standalone: true, selector: "auth-registration-finish", templateUrl: "./registration-finish.component.html", imports: [CommonModule, JslibModule, RouterModule, InputPasswordComponent], diff --git a/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts b/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts index d4ecade52fe..f98b711aeb8 100644 --- a/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts +++ b/libs/auth/src/angular/registration/registration-link-expired/registration-link-expired.component.ts @@ -21,7 +21,6 @@ export interface RegistrationLinkExpiredComponentData { } @Component({ - standalone: true, selector: "auth-registration-link-expired", templateUrl: "./registration-link-expired.component.html", imports: [CommonModule, JslibModule, RouterModule, IconModule, ButtonModule], diff --git a/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts b/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts index bc873593a83..f30dc8a3822 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start-secondary.component.ts @@ -19,7 +19,6 @@ export interface RegistrationStartSecondaryComponentData { } @Component({ - standalone: true, selector: "auth-registration-start-secondary", templateUrl: "./registration-start-secondary.component.html", imports: [CommonModule, JslibModule, RouterModule, LinkModule], diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.component.ts b/libs/auth/src/angular/registration/registration-start/registration-start.component.ts index ea756c967c6..d8a4ebb2b7d 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.component.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start.component.ts @@ -42,7 +42,6 @@ const DEFAULT_MARKETING_EMAILS_PREF_BY_REGION: Record<Region, boolean> = { }; @Component({ - standalone: true, selector: "auth-registration-start", templateUrl: "./registration-start.component.html", imports: [ diff --git a/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts b/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts index 189c669423d..a7ffca9163c 100644 --- a/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts +++ b/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts @@ -55,7 +55,6 @@ function selfHostedEnvSettingsFormValidator(): ValidatorFn { * Dialog for configuring self-hosted environment settings. */ @Component({ - standalone: true, selector: "self-hosted-env-config-dialog", templateUrl: "self-hosted-env-config-dialog.component.html", imports: [ diff --git a/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts b/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts index a28ffdbb343..fa064c9367b 100644 --- a/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts +++ b/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts @@ -30,7 +30,6 @@ import { } from "./set-password-jit.service.abstraction"; @Component({ - standalone: true, selector: "auth-set-password-jit", templateUrl: "set-password-jit.component.html", imports: [CommonModule, InputPasswordComponent, JslibModule], diff --git a/libs/auth/src/angular/sso/sso.component.ts b/libs/auth/src/angular/sso/sso.component.ts index f5a138af584..b78ca098dea 100644 --- a/libs/auth/src/angular/sso/sso.component.ts +++ b/libs/auth/src/angular/sso/sso.component.ts @@ -62,7 +62,6 @@ interface QueryParams { * This component handles the SSO flow. */ @Component({ - standalone: true, templateUrl: "sso.component.html", imports: [ AsyncActionsModule, diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts index cd4df5aee68..c53bffe2496 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts @@ -15,7 +15,6 @@ import { } from "@bitwarden/components"; @Component({ - standalone: true, selector: "app-two-factor-auth-authenticator", templateUrl: "two-factor-auth-authenticator.component.html", imports: [ diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts index 22d3906bb48..5ad70d3792d 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts @@ -26,7 +26,6 @@ import { } from "./two-factor-auth-duo-component.service"; @Component({ - standalone: true, selector: "app-two-factor-auth-duo", template: "", imports: [ diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts index 2fff77b720e..65641284cf1 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts @@ -28,7 +28,6 @@ import { TwoFactorAuthEmailComponentCacheService } from "./two-factor-auth-email import { TwoFactorAuthEmailComponentService } from "./two-factor-auth-email-component.service"; @Component({ - standalone: true, selector: "app-two-factor-auth-email", templateUrl: "two-factor-auth-email.component.html", imports: [ diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts index a8b435375db..710d5dc4de0 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts @@ -33,7 +33,6 @@ export interface WebAuthnResult { } @Component({ - standalone: true, selector: "app-two-factor-auth-webauthn", templateUrl: "two-factor-auth-webauthn.component.html", imports: [ diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts index abcbd557e0f..7218bee056c 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts @@ -15,7 +15,6 @@ import { } from "@bitwarden/components"; @Component({ - standalone: true, selector: "app-two-factor-auth-yubikey", templateUrl: "two-factor-auth-yubikey.component.html", imports: [ diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 85184283efd..034c5b82d1c 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -77,7 +77,6 @@ import { } from "./two-factor-options.component"; @Component({ - standalone: true, selector: "app-two-factor-auth", templateUrl: "two-factor-auth.component.html", imports: [ diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts index fb0d7101cad..4a5d6dfedf2 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts @@ -32,7 +32,6 @@ export type TwoFactorOptionsDialogResult = { }; @Component({ - standalone: true, selector: "app-two-factor-options", templateUrl: "two-factor-options.component.html", imports: [ diff --git a/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts b/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts index 8bb2f987d77..4dfb7a6a995 100644 --- a/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts +++ b/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts @@ -32,7 +32,6 @@ import { UserVerificationFormInputComponent } from "./user-verification-form-inp @Component({ templateUrl: "user-verification-dialog.component.html", - standalone: true, imports: [ CommonModule, ReactiveFormsModule, diff --git a/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts b/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts index 7ea191ba2f9..a14b0ef3103 100644 --- a/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts +++ b/libs/auth/src/angular/user-verification/user-verification-form-input.component.ts @@ -56,7 +56,6 @@ import { ActiveClientVerificationOption } from "./active-client-verification-opt transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]), ]), ], - standalone: true, imports: [ CommonModule, ReactiveFormsModule, diff --git a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts index c9f83902fe2..b5d87c60882 100644 --- a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts +++ b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts @@ -47,7 +47,6 @@ type VaultTimeoutFormValue = VaultTimeoutForm["value"]; @Component({ selector: "auth-vault-timeout-input", templateUrl: "vault-timeout-input.component.html", - standalone: true, imports: [CommonModule, JslibModule, ReactiveFormsModule, FormFieldModule, SelectModule], providers: [ { From d249d682fe65aa7463d386b49efbeda5375d92e9 Mon Sep 17 00:00:00 2001 From: Daniel Riera <driera@livefront.com> Date: Wed, 4 Jun 2025 09:43:38 -0400 Subject: [PATCH 239/245] PM-21736 <message> (#14902) --- apps/browser/src/autofill/notification/bar.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index e7d37f5e8d7..2027e3fecfc 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -524,6 +524,7 @@ function handleSaveCipherConfirmation(message: NotificationBarWindowMessage) { const resolvedType = resolveNotificationType(notificationBarIframeInitData); const headerMessage = getConfirmationHeaderMessage(i18n, resolvedType, error); const notificationTestId = getNotificationTestId(resolvedType, true); + appendHeaderMessageToTitle(headerMessage); globalThis.setTimeout(() => sendPlatformMessage({ command: "bgCloseNotificationBar" }), 5000); From 032fedf308ec251f17632d7d08c4daf6f41a4b1d Mon Sep 17 00:00:00 2001 From: Vijay Oommen <voommen@livefront.com> Date: Wed, 4 Jun 2025 09:04:33 -0500 Subject: [PATCH 240/245] [PM-21040] Update Ciphers after editing in Reports (#14590) --- .../pages/cipher-report.component.spec.ts | 122 ++++++++++++++++++ .../reports/pages/cipher-report.component.ts | 64 ++++++++- .../exposed-passwords-report.component.ts | 25 +++- .../inactive-two-factor-report.component.ts | 82 ++++++++---- .../reused-passwords-report.component.ts | 50 ++++++- .../unsecured-websites-report.component.ts | 17 +++ .../pages/weak-passwords-report.component.ts | 57 +++----- 7 files changed, 343 insertions(+), 74 deletions(-) create mode 100644 apps/web/src/app/dirt/reports/pages/cipher-report.component.spec.ts diff --git a/apps/web/src/app/dirt/reports/pages/cipher-report.component.spec.ts b/apps/web/src/app/dirt/reports/pages/cipher-report.component.spec.ts new file mode 100644 index 00000000000..e29ebcd1cfe --- /dev/null +++ b/apps/web/src/app/dirt/reports/pages/cipher-report.component.spec.ts @@ -0,0 +1,122 @@ +import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; +import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { DialogService } from "@bitwarden/components"; +import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; + +import { VaultItemDialogResult } from "../../../vault/components/vault-item-dialog/vault-item-dialog.component"; +import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; + +import { CipherReportComponent } from "./cipher-report.component"; + +describe("CipherReportComponent", () => { + let component: CipherReportComponent; + let mockAccountService: MockProxy<AccountService>; + let mockAdminConsoleCipherFormConfigService: MockProxy<AdminConsoleCipherFormConfigService>; + const mockCipher = { + id: "122-333-444", + type: CipherType.Login, + orgId: "222-444-555", + login: { + username: "test-username", + password: "test-password", + totp: "123", + }, + decrypt: jest.fn().mockResolvedValue({ id: "cipher1", name: "Updated" }), + } as unknown as Cipher; + const mockCipherService = mock<CipherService>(); + mockCipherService.get.mockResolvedValue(mockCipher as unknown as Cipher); + mockCipherService.getKeyForCipherKeyDecryption.mockResolvedValue({}); + mockCipherService.deleteWithServer.mockResolvedValue(undefined); + mockCipherService.softDeleteWithServer.mockResolvedValue(undefined); + + beforeEach(() => { + mockAccountService = mock<AccountService>(); + mockAccountService.activeAccount$ = of({ id: "user1" } as any); + mockAdminConsoleCipherFormConfigService = mock<AdminConsoleCipherFormConfigService>(); + + component = new CipherReportComponent( + mockCipherService, + mock<DialogService>(), + mock<PasswordRepromptService>(), + mock<OrganizationService>(), + mockAccountService, + mock<I18nService>(), + mock<SyncService>(), + mock<CipherFormConfigService>(), + mockAdminConsoleCipherFormConfigService, + ); + component.ciphers = []; + component.allCiphers = []; + }); + + it("should remove the cipher from the report if it was deleted", async () => { + const cipherToDelete = { id: "cipher1" } as any; + component.ciphers = [cipherToDelete, { id: "cipher2" } as any]; + + jest.spyOn(component, "determinedUpdatedCipherReportStatus").mockResolvedValue(null); + + await component.refresh(VaultItemDialogResult.Deleted, cipherToDelete); + + expect(component.ciphers).toEqual([{ id: "cipher2" }]); + expect(component.determinedUpdatedCipherReportStatus).toHaveBeenCalledWith( + VaultItemDialogResult.Deleted, + cipherToDelete, + ); + }); + + it("should update the cipher in the report if it was saved", async () => { + const cipherViewToUpdate = { ...mockCipher } as unknown as CipherView; + const updatedCipher = { ...mockCipher, name: "Updated" } as unknown as Cipher; + const updatedCipherView = { ...updatedCipher } as unknown as CipherView; + + component.ciphers = [cipherViewToUpdate]; + mockCipherService.get.mockResolvedValue(updatedCipher); + mockCipherService.getKeyForCipherKeyDecryption.mockResolvedValue("key"); + + jest.spyOn(updatedCipher, "decrypt").mockResolvedValue(updatedCipherView); + + jest + .spyOn(component, "determinedUpdatedCipherReportStatus") + .mockResolvedValue(updatedCipherView); + + await component.refresh(VaultItemDialogResult.Saved, updatedCipherView); + + expect(component.ciphers).toEqual([updatedCipherView]); + expect(component.determinedUpdatedCipherReportStatus).toHaveBeenCalledWith( + VaultItemDialogResult.Saved, + updatedCipherView, + ); + }); + + it("should remove the cipher from the report if it no longer meets the criteria after saving", async () => { + const cipherViewToUpdate = { ...mockCipher } as unknown as CipherView; + const updatedCipher = { ...mockCipher, name: "Updated" } as unknown as Cipher; + const updatedCipherView = { ...updatedCipher } as unknown as CipherView; + + component.ciphers = [cipherViewToUpdate]; + + mockCipherService.get.mockResolvedValue(updatedCipher); + mockCipherService.getKeyForCipherKeyDecryption.mockResolvedValue("key"); + + jest.spyOn(updatedCipher, "decrypt").mockResolvedValue(updatedCipherView); + + jest.spyOn(component, "determinedUpdatedCipherReportStatus").mockResolvedValue(null); + + await component.refresh(VaultItemDialogResult.Saved, updatedCipherView); + + expect(component.ciphers).toEqual([]); + expect(component.determinedUpdatedCipherReportStatus).toHaveBeenCalledWith( + VaultItemDialogResult.Saved, + updatedCipherView, + ); + }); +}); diff --git a/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts b/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts index d6c96ff232e..69dd360ad31 100644 --- a/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts @@ -213,8 +213,68 @@ export class CipherReportComponent implements OnDestroy { this.allCiphers = []; } - protected async refresh(result: VaultItemDialogResult, cipher: CipherView) { - await this.load(); + async refresh(result: VaultItemDialogResult, cipher: CipherView) { + if (result === VaultItemDialogResult.Deleted) { + // update downstream report status if the cipher was deleted + await this.determinedUpdatedCipherReportStatus(result, cipher); + + // the cipher was deleted, filter it out from the report. + this.ciphers = this.ciphers.filter((ciph) => ciph.id !== cipher.id); + this.filterCiphersByOrg(this.ciphers); + return; + } + + if (result == VaultItemDialogResult.Saved) { + // Ensure we have the latest cipher data after saving. + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + let updatedCipher = await this.cipherService.get(cipher.id, activeUserId); + + if (this.isAdminConsoleActive) { + updatedCipher = await this.adminConsoleCipherFormConfigService.getCipher( + cipher.id as CipherId, + this.organization, + ); + } + + // convert cipher to cipher view model + const updatedCipherView = await updatedCipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), + ); + + // request downstream report status if the cipher was updated + // this will return a null if the updated cipher does not meet the criteria for the report + const updatedReportResult = await this.determinedUpdatedCipherReportStatus( + result, + updatedCipherView, + ); + + // determine the index of the updated cipher in the report + const index = this.ciphers.findIndex((c) => c.id === updatedCipherView.id); + + // the updated cipher does not meet the criteria for the report, it returns a null + if (updatedReportResult === null) { + this.ciphers.splice(index, 1); + } + + // the cipher is already in the report, update it. + if (updatedReportResult !== null && index > -1) { + this.ciphers[index] = updatedReportResult; + } + + // apply filters and set the data source + this.filterCiphersByOrg(this.ciphers); + } + } + + async determinedUpdatedCipherReportStatus( + result: VaultItemDialogResult, + updatedCipherView: CipherView, + ): Promise<CipherView | null> { + // Implement the logic to determine if the updated cipher is still in the report. + // This could be checking if the password is still weak or exposed, etc. + // For now, we will return the updated cipher view as is. + // Replace this with your actual logic in the child classes. + return updatedCipherView; } protected async repromptCipher(c: CipherView) { diff --git a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts index 5710ea1176e..1a4141c4d68 100644 --- a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts @@ -10,6 +10,7 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; +import { VaultItemDialogResult } from "@bitwarden/web-vault/app/vault/components/vault-item-dialog/vault-item-dialog.component"; import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; @@ -73,10 +74,9 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple return; } - const promise = this.auditService.passwordLeaked(login.password).then((exposedCount) => { - if (exposedCount > 0) { - const row = { ...ciph, exposedXTimes: exposedCount } as ReportResult; - exposedPasswordCiphers.push(row); + const promise = this.isPasswordExposed(ciph).then((result) => { + if (result) { + exposedPasswordCiphers.push(result); } }); promises.push(promise); @@ -87,8 +87,25 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple this.dataSource.sort = { column: "exposedXTimes", direction: "desc" }; } + private async isPasswordExposed(cv: CipherView): Promise<ReportResult | null> { + const { login } = cv; + return await this.auditService.passwordLeaked(login.password).then((exposedCount) => { + if (exposedCount > 0) { + return { ...cv, exposedXTimes: exposedCount } as ReportResult; + } + return null; + }); + } + protected canManageCipher(c: CipherView): boolean { // this will only ever be false from the org view; return true; } + + async determinedUpdatedCipherReportStatus( + result: VaultItemDialogResult, + updatedCipherView: CipherView, + ): Promise<CipherView | null> { + return await this.isPasswordExposed(updatedCipherView); + } } diff --git a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts index 95810625dac..0024af35109 100644 --- a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts @@ -13,6 +13,7 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; +import { VaultItemDialogResult } from "@bitwarden/web-vault/app/vault/components/vault-item-dialog/vault-item-dialog.component"; import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; @@ -71,32 +72,12 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl this.filterStatus = [0]; allCiphers.forEach((ciph) => { - const { type, login, isDeleted, edit, id, viewPassword } = ciph; - if ( - type !== CipherType.Login || - (login.totp != null && login.totp !== "") || - !login.hasUris || - isDeleted || - (!this.organization && !edit) || - !viewPassword - ) { - return; - } + const [docFor2fa, isInactive2faCipher] = this.isInactive2faCipher(ciph); - for (let i = 0; i < login.uris.length; i++) { - const u = login.uris[i]; - if (u.uri != null && u.uri !== "") { - const uri = u.uri.replace("www.", ""); - const domain = Utils.getDomain(uri); - if (domain != null && this.services.has(domain)) { - if (this.services.get(domain) != null) { - docs.set(id, this.services.get(domain)); - } - // If the uri is in the 2fa list. Add the cipher to the inactive - // collection. No need to check any additional uris for the cipher. - inactive2faCiphers.push(ciph); - return; - } + if (isInactive2faCipher) { + inactive2faCiphers.push(ciph); + if (docFor2fa !== "") { + docs.set(ciph.id, docFor2fa); } } }); @@ -106,6 +87,39 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl } } + private isInactive2faCipher(cipher: CipherView): [string, boolean] { + let docFor2fa: string = ""; + let isInactive2faCipher: boolean = false; + + const { type, login, isDeleted, edit, viewPassword } = cipher; + if ( + type !== CipherType.Login || + (login.totp != null && login.totp !== "") || + !login.hasUris || + isDeleted || + (!this.organization && !edit) || + !viewPassword + ) { + return [docFor2fa, isInactive2faCipher]; + } + + for (let i = 0; i < login.uris.length; i++) { + const u = login.uris[i]; + if (u.uri != null && u.uri !== "") { + const uri = u.uri.replace("www.", ""); + const domain = Utils.getDomain(uri); + if (domain != null && this.services.has(domain)) { + if (this.services.get(domain) != null) { + docFor2fa = this.services.get(domain) || ""; + } + isInactive2faCipher = true; + break; + } + } + } + return [docFor2fa, isInactive2faCipher]; + } + private async load2fa() { if (this.services.size > 0) { return; @@ -142,4 +156,22 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl // this will only ever be false from the org view; return true; } + + async determinedUpdatedCipherReportStatus( + result: VaultItemDialogResult, + updatedCipherView: CipherView, + ): Promise<CipherView | null> { + if (result === VaultItemDialogResult.Deleted) { + return null; + } + + const [docFor2fa, isInactive2faCipher] = this.isInactive2faCipher(updatedCipherView); + + if (isInactive2faCipher) { + this.cipherDocs.set(updatedCipherView.id, docFor2fa); + return updatedCipherView; + } + + return null; + } } diff --git a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts index 3e9abc779ba..8e1e4fcf0cc 100644 --- a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts @@ -11,6 +11,7 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; +import { VaultItemDialogResult } from "@bitwarden/web-vault/app/vault/components/vault-item-dialog/vault-item-dialog.component"; import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; @@ -22,6 +23,7 @@ import { CipherReportComponent } from "./cipher-report.component"; standalone: false, }) export class ReusedPasswordsReportComponent extends CipherReportComponent implements OnInit { + ciphersToCheckForReusedPasswords: CipherView[] = []; passwordUseMap: Map<string, number>; disabled = true; @@ -54,12 +56,19 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem } async setCiphers() { - const allCiphers = await this.getAllCiphers(); + this.ciphersToCheckForReusedPasswords = await this.getAllCiphers(); + const reusedPasswordCiphers = await this.checkCiphersForReusedPasswords( + this.ciphersToCheckForReusedPasswords, + ); + this.filterCiphersByOrg(reusedPasswordCiphers); + } + + protected async checkCiphersForReusedPasswords(ciphers: CipherView[]): Promise<CipherView[]> { const ciphersWithPasswords: CipherView[] = []; this.passwordUseMap = new Map<string, number>(); this.filterStatus = [0]; - allCiphers.forEach((ciph) => { + ciphers.forEach((ciph) => { const { type, login, isDeleted, edit, viewPassword } = ciph; if ( type !== CipherType.Login || @@ -84,11 +93,46 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem this.passwordUseMap.has(c.login.password) && this.passwordUseMap.get(c.login.password) > 1, ); - this.filterCiphersByOrg(reusedPasswordCiphers); + return reusedPasswordCiphers; } protected canManageCipher(c: CipherView): boolean { // this will only ever be false from an organization view return true; } + + async determinedUpdatedCipherReportStatus( + result: VaultItemDialogResult, + updatedCipherView: CipherView, + ): Promise<CipherView | null> { + if (result === VaultItemDialogResult.Deleted) { + this.ciphersToCheckForReusedPasswords = this.ciphersToCheckForReusedPasswords.filter( + (c) => c.id !== updatedCipherView.id, + ); + return null; + } + + // recalculate the reused passwords after an update + // if a password was changed, it could affect reused counts of other ciphers + + // find the cipher in our list and update it + const index = this.ciphersToCheckForReusedPasswords.findIndex( + (c) => c.id === updatedCipherView.id, + ); + + if (index !== -1) { + this.ciphersToCheckForReusedPasswords[index] = updatedCipherView; + } + + // Re-check the passwords for reused passwords for all ciphers + const reusedPasswordCiphers = await this.checkCiphersForReusedPasswords( + this.ciphersToCheckForReusedPasswords, + ); + + // set the updated ciphers list to the filtered reused passwords + this.filterCiphersByOrg(reusedPasswordCiphers); + + // return the updated cipher view + return updatedCipherView; + } } diff --git a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts index d2cc792198e..4b9cc3fd789 100644 --- a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts @@ -10,6 +10,7 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; +import { VaultItemDialogResult } from "@bitwarden/web-vault/app/vault/components/vault-item-dialog/vault-item-dialog.component"; import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; @@ -93,4 +94,20 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl // this will only ever be false from the org view; return true; } + + async determinedUpdatedCipherReportStatus( + result: VaultItemDialogResult, + updatedCipherView: CipherView, + ): Promise<CipherView | null> { + if (result === VaultItemDialogResult.Deleted) { + return null; + } + + // If the cipher still contains unsecured URIs, return it as is + if (this.cipherContainsUnsecured(updatedCipherView)) { + return updatedCipherView; + } + + return null; + } } diff --git a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts index 1716a98190c..0472dbfaa6f 100644 --- a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts @@ -1,15 +1,12 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; -import { firstValueFrom } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; -import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -71,46 +68,26 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen this.findWeakPasswords(allCiphers); } - protected async refresh(result: VaultItemDialogResult, cipher: CipherView) { + async determinedUpdatedCipherReportStatus( + result: VaultItemDialogResult, + updatedCipherView: CipherView, + ): Promise<CipherView | null> { if (result === VaultItemDialogResult.Deleted) { - // remove the cipher from the list - this.weakPasswordCiphers = this.weakPasswordCiphers.filter((c) => c.id !== cipher.id); - this.filterCiphersByOrg(this.weakPasswordCiphers); - return; - } - - if (result == VaultItemDialogResult.Saved) { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - let updatedCipher = await this.cipherService.get(cipher.id, activeUserId); - - if (this.isAdminConsoleActive) { - updatedCipher = await this.adminConsoleCipherFormConfigService.getCipher( - cipher.id as CipherId, - this.organization, - ); - } - - const updatedCipherView = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), + this.weakPasswordCiphers = this.weakPasswordCiphers.filter( + (c) => c.id !== updatedCipherView.id, ); - // update the cipher views - const updatedReportResult = this.determineWeakPasswordScore(updatedCipherView); - const index = this.weakPasswordCiphers.findIndex((c) => c.id === updatedCipherView.id); - - if (updatedReportResult == null) { - // the password is no longer weak - // remove the cipher from the list - this.weakPasswordCiphers.splice(index, 1); - this.filterCiphersByOrg(this.weakPasswordCiphers); - return; - } - - if (index > -1) { - // update the existing cipher - this.weakPasswordCiphers[index] = updatedReportResult; - this.filterCiphersByOrg(this.weakPasswordCiphers); - } + return null; } + + const updatedReportStatus = await this.determineWeakPasswordScore(updatedCipherView); + + const index = this.weakPasswordCiphers.findIndex((c) => c.id === updatedCipherView.id); + + if (index !== -1) { + this.weakPasswordCiphers[index] = updatedReportStatus; + } + + return updatedReportStatus; } protected findWeakPasswords(ciphers: CipherView[]): void { From 5bd3ab5b3fc1e040046b51d102f480b61b4b7779 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Wed, 4 Jun 2025 19:11:35 +0200 Subject: [PATCH 241/245] Remove unneeded setup code for import.service tests (#15069) Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> --- .../src/services/import.service.spec.ts | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/libs/importer/src/services/import.service.spec.ts b/libs/importer/src/services/import.service.spec.ts index 6c8656f4c1d..f71c34bf209 100644 --- a/libs/importer/src/services/import.service.spec.ts +++ b/libs/importer/src/services/import.service.spec.ts @@ -112,10 +112,6 @@ describe("ImportService", () => { mockImportTargetFolder.name = "myImportTarget"; it("passing importTarget adds it to folders", async () => { - folderService.getAllDecryptedFromState.mockReturnValue( - Promise.resolve([mockImportTargetFolder]), - ); - await importService["setImportTarget"](importResult, null, mockImportTargetFolder); expect(importResult.folders.length).toBe(1); expect(importResult.folders[0]).toBe(mockImportTargetFolder); @@ -130,12 +126,6 @@ describe("ImportService", () => { mockFolder2.name = "folder2"; it("passing importTarget sets it as new root for all existing folders", async () => { - folderService.getAllDecryptedFromState.mockResolvedValue([ - mockImportTargetFolder, - mockFolder1, - mockFolder2, - ]); - importResult.folders.push(mockFolder1); importResult.folders.push(mockFolder2); @@ -166,11 +156,6 @@ describe("ImportService", () => { mockCollection1.organizationId = organizationId; it("passing importTarget adds it to collections", async () => { - collectionService.getAllDecrypted.mockResolvedValue([ - mockImportTargetCollection, - mockCollection1, - ]); - await importService["setImportTarget"]( importResult, organizationId, @@ -181,12 +166,6 @@ describe("ImportService", () => { }); it("passing importTarget sets it as new root for all existing collections", async () => { - collectionService.getAllDecrypted.mockResolvedValue([ - mockImportTargetCollection, - mockCollection1, - mockCollection2, - ]); - importResult.collections.push(mockCollection1); importResult.collections.push(mockCollection2); @@ -226,12 +205,6 @@ describe("ImportService", () => { }); it("passing importTarget, collectionRelationship has the expected values", async () => { - collectionService.getAllDecrypted.mockResolvedValue([ - mockImportTargetCollection, - mockCollection1, - mockCollection2, - ]); - importResult.ciphers.push(createCipher({ name: "cipher1" })); importResult.ciphers.push(createCipher({ name: "cipher2" })); importResult.collectionRelationships.push([0, 0]); @@ -249,12 +222,6 @@ describe("ImportService", () => { }); it("passing importTarget, folderRelationship has the expected values", async () => { - folderService.getAllDecryptedFromState.mockResolvedValue([ - mockImportTargetFolder, - mockFolder1, - mockFolder2, - ]); - importResult.folders.push(mockFolder1); importResult.folders.push(mockFolder2); From 2a17de6dd102d3092e8d849bde7b8e5ffa316c8e Mon Sep 17 00:00:00 2001 From: Joost Meulenbeld <joost.meulenbeld@gmail.com> Date: Wed, 4 Jun 2025 19:20:19 +0200 Subject: [PATCH 242/245] Update desktop.yml to include flatpak as installation method option (#15068) --- .github/ISSUE_TEMPLATE/desktop.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/desktop.yml b/.github/ISSUE_TEMPLATE/desktop.yml index 6fd6f1d1c2b..129ba510664 100644 --- a/.github/ISSUE_TEMPLATE/desktop.yml +++ b/.github/ISSUE_TEMPLATE/desktop.yml @@ -73,6 +73,7 @@ body: - Homebrew - Chocolatey - Snap + - Flatpak - Other validations: required: true From a17cc0b265dc75abfa1ce5d46279f596e0294c8f Mon Sep 17 00:00:00 2001 From: Will Martin <contact@willmartian.com> Date: Wed, 4 Jun 2025 15:21:17 -0400 Subject: [PATCH 243/245] [CL-640] update bit-simple-dialog styles (#14916) --- .../dialog/simple-dialog/simple-dialog.component.html | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.component.html b/libs/components/src/dialog/simple-dialog/simple-dialog.component.html index d810838cabb..47cd396a239 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.component.html +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.component.html @@ -1,8 +1,8 @@ <div - class="tw-my-4 tw-flex tw-max-h-screen tw-w-96 tw-max-w-90vw tw-flex-col tw-overflow-hidden tw-rounded-3xl tw-border tw-border-solid tw-border-secondary-300 tw-bg-text-contrast tw-text-main" + class="tw-my-4 tw-pb-6 tw-pt-8 tw-flex tw-max-h-screen tw-w-96 tw-max-w-90vw tw-flex-col tw-overflow-hidden tw-rounded-3xl tw-border tw-border-solid tw-border-secondary-100 tw-shadow-xl tw-bg-text-contrast tw-text-main" @fadeIn > - <div class="tw-flex tw-flex-col tw-items-center tw-gap-2 tw-px-4 tw-pt-4 tw-text-center"> + <div class="tw-flex tw-px-6 tw-flex-col tw-items-center tw-gap-2 tw-text-center"> @if (!hideIcon()) { @if (hasIcon) { <ng-content select="[bitDialogIcon]"></ng-content> @@ -20,13 +20,11 @@ </h1> </div> <div - class="tw-overflow-y-auto tw-px-4 tw-pb-4 tw-text-center tw-text-base tw-break-words tw-hyphens-auto" + class="tw-overflow-y-auto tw-px-6 tw-mb-6 tw-text-center tw-text-base tw-break-words tw-hyphens-auto" > <ng-content select="[bitDialogContent]"></ng-content> </div> - <div - class="tw-flex tw-flex-row tw-gap-2 tw-border-0 tw-border-t tw-border-solid tw-border-secondary-300 tw-p-4" - > + <div class="tw-flex tw-flex-col tw-gap-2 tw-px-6"> <ng-content select="[bitDialogFooter]"></ng-content> </div> </div> From 0032d1457f90569af90eb54cc64f674b127b7c97 Mon Sep 17 00:00:00 2001 From: Vijay Oommen <voommen@livefront.com> Date: Wed, 4 Jun 2025 14:33:46 -0500 Subject: [PATCH 244/245] [PM-21713] Include CipherId and find Ciphers in Risk Insights report (#14823) --- .../risk-insights/models/password-health.ts | 7 +++- .../services/critical-apps.service.ts | 5 +++ .../services/risk-insights-report.service.ts | 21 +++++++++- .../all-applications.component.ts | 40 ++++++++++++++----- .../app-table-row-scrollable.component.html | 2 +- .../app-table-row-scrollable.component.ts | 4 +- .../critical-applications.component.ts | 18 ++++++++- 7 files changed, 80 insertions(+), 17 deletions(-) diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts index acb4a116b8f..62eb0122dca 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts @@ -32,13 +32,18 @@ export type ApplicationHealthReportDetail = { atRiskMemberCount: number; memberDetails: MemberDetailsFlat[]; atRiskMemberDetails: MemberDetailsFlat[]; - cipher: CipherView; + cipherIds: string[]; }; export type ApplicationHealthReportDetailWithCriticalFlag = ApplicationHealthReportDetail & { isMarkedAsCritical: boolean; }; +export type ApplicationHealthReportDetailWithCriticalFlagAndCipher = + ApplicationHealthReportDetailWithCriticalFlag & { + ciphers: CipherView[]; + }; + /** * Breaks the cipher health info out by uri and passes * along the password health and member info diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/critical-apps.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/critical-apps.service.ts index b879ef94705..6ad1cb71051 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/critical-apps.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/critical-apps.service.ts @@ -141,6 +141,11 @@ export class CriticalAppsService { const uri = await this.encryptService.decryptString(encrypted, key); return { id: r.id, organizationId: r.organizationId, uri: uri }; }); + + if (results.length === 0) { + return of([]); // emits an empty array immediately + } + return forkJoin(results); }), first(), diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts index afd246e1836..182e8aa6882 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts @@ -20,6 +20,7 @@ import { MemberDetailsFlat, WeakPasswordDetail, WeakPasswordScore, + ApplicationHealthReportDetailWithCriticalFlagAndCipher, } from "../models/password-health"; import { MemberCipherDetailsApiService } from "./member-cipher-details-api.service"; @@ -164,6 +165,22 @@ export class RiskInsightsReportService { }; } + async identifyCiphers( + data: ApplicationHealthReportDetail[], + organizationId: string, + ): Promise<ApplicationHealthReportDetailWithCriticalFlagAndCipher[]> { + const cipherViews = await this.cipherService.getAllFromApiForOrganization(organizationId); + + const dataWithCiphers = data.map( + (app, index) => + ({ + ...app, + ciphers: cipherViews.filter((c) => app.cipherIds.some((a) => a === c.id)), + }) as ApplicationHealthReportDetailWithCriticalFlagAndCipher, + ); + return dataWithCiphers; + } + /** * Associates the members with the ciphers they have access to. Calculates the password health. * Finds the trimmed uris. @@ -358,7 +375,9 @@ export class RiskInsightsReportService { atRiskPasswordCount: existingUriDetail ? existingUriDetail.atRiskPasswordCount : 0, atRiskCipherIds: existingUriDetail ? existingUriDetail.atRiskCipherIds : [], atRiskMemberCount: existingUriDetail ? existingUriDetail.atRiskMemberDetails.length : 0, - cipher: newUriDetail.cipher, + cipherIds: existingUriDetail + ? existingUriDetail.cipherIds.concat(newUriDetail.cipherId) + : [newUriDetail.cipherId], } as ApplicationHealthReportDetail; if (isAtRisk) { diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts index b5f5773727f..ee08ec6661e 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts @@ -2,7 +2,7 @@ import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; -import { combineLatest, debounceTime, firstValueFrom, map, Observable, of, skipWhile } from "rxjs"; +import { combineLatest, debounceTime, firstValueFrom, map, Observable, of, switchMap } from "rxjs"; import { CriticalAppsService, @@ -12,6 +12,7 @@ import { import { ApplicationHealthReportDetail, ApplicationHealthReportDetailWithCriticalFlag, + ApplicationHealthReportDetailWithCriticalFlagAndCipher, ApplicationHealthReportSummary, } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health"; import { @@ -56,7 +57,8 @@ import { ApplicationsLoadingComponent } from "./risk-insights-loading.component" ], }) export class AllApplicationsComponent implements OnInit { - protected dataSource = new TableDataSource<ApplicationHealthReportDetailWithCriticalFlag>(); + protected dataSource = + new TableDataSource<ApplicationHealthReportDetailWithCriticalFlagAndCipher>(); protected selectedUrls: Set<string> = new Set<string>(); protected searchControl = new FormControl("", { nonNullable: true }); protected loading = true; @@ -74,7 +76,7 @@ export class AllApplicationsComponent implements OnInit { isLoading$: Observable<boolean> = of(false); async ngOnInit() { - const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId") ?? ""; + const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId"); const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); if (organizationId) { @@ -89,14 +91,32 @@ export class AllApplicationsComponent implements OnInit { ]) .pipe( takeUntilDestroyed(this.destroyRef), - skipWhile(([_, __, organization]) => !organization), map(([applications, criticalApps, organization]) => { - const criticalUrls = criticalApps.map((ca) => ca.uri); - const data = applications?.map((app) => ({ - ...app, - isMarkedAsCritical: criticalUrls.includes(app.applicationName), - })) as ApplicationHealthReportDetailWithCriticalFlag[]; - return { data, organization }; + if (applications && applications.length === 0 && criticalApps && criticalApps) { + const criticalUrls = criticalApps.map((ca) => ca.uri); + const data = applications?.map((app) => ({ + ...app, + isMarkedAsCritical: criticalUrls.includes(app.applicationName), + })) as ApplicationHealthReportDetailWithCriticalFlag[]; + return { data, organization }; + } + + return { data: applications, organization }; + }), + switchMap(async ({ data, organization }) => { + if (data && organization) { + const dataWithCiphers = await this.reportService.identifyCiphers( + data, + organization.id, + ); + + return { + data: dataWithCiphers, + organization, + }; + } + + return { data: [], organization }; }), ) .subscribe(({ data, organization }) => { diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html index 383780b450c..97c5b187ef9 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html @@ -32,7 +32,7 @@ <i class="bwi bwi-star-f" *ngIf="row.isMarkedAsCritical"></i> </td> <td bitCell> - <app-vault-icon [cipher]="row.cipher"></app-vault-icon> + <app-vault-icon *ngIf="row.ciphers.length > 0" [cipher]="row.ciphers[0]"></app-vault-icon> </td> <td class="tw-cursor-pointer" diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts index 1b3970d7b04..a729f21158f 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.ts @@ -2,7 +2,7 @@ import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ApplicationHealthReportDetailWithCriticalFlag } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health"; +import { ApplicationHealthReportDetailWithCriticalFlagAndCipher } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health"; import { MenuModule, TableDataSource, TableModule } from "@bitwarden/components"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; @@ -13,7 +13,7 @@ import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pip templateUrl: "./app-table-row-scrollable.component.html", }) export class AppTableRowScrollableComponent { - @Input() dataSource!: TableDataSource<ApplicationHealthReportDetailWithCriticalFlag>; + @Input() dataSource!: TableDataSource<ApplicationHealthReportDetailWithCriticalFlagAndCipher>; @Input() showRowMenuForCriticalApps: boolean = false; @Input() showRowCheckBox: boolean = false; @Input() selectedUrls: Set<string> = new Set<string>(); diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts index 183869c55fa..765d979bbe6 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications.component.ts @@ -4,7 +4,7 @@ import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { combineLatest, debounceTime, map } from "rxjs"; +import { combineLatest, debounceTime, map, switchMap } from "rxjs"; import { CriticalAppsService, @@ -13,6 +13,7 @@ import { } from "@bitwarden/bit-common/dirt/reports/risk-insights"; import { ApplicationHealthReportDetailWithCriticalFlag, + ApplicationHealthReportDetailWithCriticalFlagAndCipher, ApplicationHealthReportSummary, } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -53,7 +54,8 @@ import { RiskInsightsTabType } from "./risk-insights.component"; providers: [DefaultAdminTaskService], }) export class CriticalApplicationsComponent implements OnInit { - protected dataSource = new TableDataSource<ApplicationHealthReportDetailWithCriticalFlag>(); + protected dataSource = + new TableDataSource<ApplicationHealthReportDetailWithCriticalFlagAndCipher>(); protected selectedIds: Set<number> = new Set<number>(); protected searchControl = new FormControl("", { nonNullable: true }); private destroyRef = inject(DestroyRef); @@ -68,7 +70,9 @@ export class CriticalApplicationsComponent implements OnInit { this.isNotificationsFeatureEnabled = await this.configService.getFeatureFlag( FeatureFlag.EnableRiskInsightsNotifications, ); + this.organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId") ?? ""; + combineLatest([ this.dataService.applications$, this.criticalAppsService.getAppsListForOrg(this.organizationId), @@ -83,6 +87,16 @@ export class CriticalApplicationsComponent implements OnInit { })) as ApplicationHealthReportDetailWithCriticalFlag[]; return data?.filter((app) => app.isMarkedAsCritical); }), + switchMap(async (data) => { + if (data) { + const dataWithCiphers = await this.reportService.identifyCiphers( + data, + this.organizationId, + ); + return dataWithCiphers; + } + return null; + }), ) .subscribe((applications) => { if (applications) { From 7386a4fa9e4c1119da11fed99f3e729e5e5191b5 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Wed, 4 Jun 2025 15:51:43 -0700 Subject: [PATCH 245/245] [PM-19306] - [Vault] In Admin Console Policies area add the remove card item type policy (#15065) * WIP - add restricted item types policy * admin console restricted item types * add comment * update feature flag * fix comment --- .../organizations/policies/index.ts | 1 + .../policies/policies.component.ts | 11 ++++++++- .../organizations/policies/policies.module.ts | 2 ++ .../restricted-item-types.component.html | 6 +++++ .../restricted-item-types.component.ts | 23 +++++++++++++++++++ apps/web/src/locales/en/messages.json | 6 +++++ .../admin-console/enums/policy-type.enum.ts | 1 + 7 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.html create mode 100644 apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.ts diff --git a/apps/web/src/app/admin-console/organizations/policies/index.ts b/apps/web/src/app/admin-console/organizations/policies/index.ts index 20137105993..4f4b85fc6c2 100644 --- a/apps/web/src/app/admin-console/organizations/policies/index.ts +++ b/apps/web/src/app/admin-console/organizations/policies/index.ts @@ -11,3 +11,4 @@ export { SingleOrgPolicy } from "./single-org.component"; export { TwoFactorAuthenticationPolicy } from "./two-factor-authentication.component"; export { PoliciesComponent } from "./policies.component"; export { RemoveUnlockWithPinPolicy } from "./remove-unlock-with-pin.component"; +export { RestrictedItemTypesPolicy } from "./restricted-item-types.component"; diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts index 73f0d99b4f9..8b6894871bd 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts @@ -15,6 +15,8 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { DialogService } from "@bitwarden/components"; import { ChangePlanDialogResultType, @@ -23,7 +25,7 @@ import { import { All } from "@bitwarden/web-vault/app/vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model"; import { PolicyListService } from "../../core/policy-list.service"; -import { BasePolicy } from "../policies"; +import { BasePolicy, RestrictedItemTypesPolicy } from "../policies"; import { CollectionDialogTabType } from "../shared/components/collection-dialog"; import { PolicyEditComponent, PolicyEditDialogResult } from "./policy-edit.component"; @@ -51,6 +53,7 @@ export class PoliciesComponent implements OnInit { private policyListService: PolicyListService, private organizationBillingService: OrganizationBillingServiceAbstraction, private dialogService: DialogService, + private configService: ConfigService, ) {} async ngOnInit() { @@ -91,6 +94,12 @@ export class PoliciesComponent implements OnInit { } async load() { + if ( + (await this.configService.getFeatureFlag(FeatureFlag.RemoveCardItemTypePolicy)) && + this.policyListService.getPolicies().every((p) => !(p instanceof RestrictedItemTypesPolicy)) + ) { + this.policyListService.addPolicies([new RestrictedItemTypesPolicy()]); + } const response = await this.policyApiService.getPolicies(this.organizationId); this.orgPolicies = response.data != null && response.data.length > 0 ? response.data : []; this.orgPolicies.forEach((op) => { diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.module.ts b/apps/web/src/app/admin-console/organizations/policies/policies.module.ts index 1b8ec5089f9..4ecf8d76491 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.module.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.module.ts @@ -11,6 +11,7 @@ import { PolicyEditComponent } from "./policy-edit.component"; import { RemoveUnlockWithPinPolicyComponent } from "./remove-unlock-with-pin.component"; import { RequireSsoPolicyComponent } from "./require-sso.component"; import { ResetPasswordPolicyComponent } from "./reset-password.component"; +import { RestrictedItemTypesPolicyComponent } from "./restricted-item-types.component"; import { SendOptionsPolicyComponent } from "./send-options.component"; import { SingleOrgPolicyComponent } from "./single-org.component"; import { TwoFactorAuthenticationPolicyComponent } from "./two-factor-authentication.component"; @@ -30,6 +31,7 @@ import { TwoFactorAuthenticationPolicyComponent } from "./two-factor-authenticat PoliciesComponent, PolicyEditComponent, RemoveUnlockWithPinPolicyComponent, + RestrictedItemTypesPolicyComponent, ], exports: [ DisableSendPolicyComponent, diff --git a/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.html b/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.html new file mode 100644 index 00000000000..8665cc1fa6a --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.html @@ -0,0 +1,6 @@ +<bit-form-control> + <input type="checkbox" bitCheckbox [formControl]="enabled" id="enabled" /> + <bit-label>{{ "turnOn" | i18n }}</bit-label> +</bit-form-control> +<!-- To allow for multiple item types we can add a data formGroup, iterate over the + cipher types as checkboxes/multi-select and use that as a means to track which types are restricted --> diff --git a/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.ts b/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.ts new file mode 100644 index 00000000000..8dd8720a220 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.ts @@ -0,0 +1,23 @@ +import { Component } from "@angular/core"; + +import { PolicyType } from "@bitwarden/common/admin-console/enums"; + +import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; + +export class RestrictedItemTypesPolicy extends BasePolicy { + name = "restrictedItemTypesPolicy"; + description = "restrictedItemTypesPolicyDesc"; + type = PolicyType.RestrictedItemTypesPolicy; + component = RestrictedItemTypesPolicyComponent; +} + +@Component({ + selector: "policy-restricted-item-types", + templateUrl: "restricted-item-types.component.html", + standalone: false, +}) +export class RestrictedItemTypesPolicyComponent extends BasePolicyComponent { + constructor() { + super(); + } +} diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 7735286856b..33468e0b306 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -2154,6 +2154,12 @@ "twoStepLoginRecoveryWarning": { "message": "Setting up two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (example: you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place." }, + "restrictedItemTypesPolicy": { + "message": "Remove card item type" + }, + "restrictedItemTypesPolicyDesc": { + "message": "Do not allow members to create card item types." + }, "yourSingleUseRecoveryCode": { "message": "Your single-use recovery code can be used to turn off two-step login in the event that you lose access to your two-step login provider. Bitwarden recommends you write down the recovery code and keep it in a safe place." }, diff --git a/libs/common/src/admin-console/enums/policy-type.enum.ts b/libs/common/src/admin-console/enums/policy-type.enum.ts index 42ab798eabf..2f70906fb60 100644 --- a/libs/common/src/admin-console/enums/policy-type.enum.ts +++ b/libs/common/src/admin-console/enums/policy-type.enum.ts @@ -16,4 +16,5 @@ export enum PolicyType { AutomaticAppLogIn = 12, // Enables automatic log in of apps from configured identity provider FreeFamiliesSponsorshipPolicy = 13, // Disables free families plan for organization RemoveUnlockWithPin = 14, // Do not allow members to unlock their account with a PIN. + RestrictedItemTypesPolicy = 15, // Restricts item types that can be created within an organization }