From 13940a74ae88e146286f40e9c6dec164d11c21db Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sat, 22 Nov 2025 11:53:45 +0100 Subject: [PATCH 01/13] Fix biometrics unlock when pin is enabled (#17528) --- .../browser/src/background/main.background.ts | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index f59b6648486..fecc47af981 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -726,17 +726,6 @@ export default class MainBackground { const pinStateService = new PinStateService(this.stateProvider); - this.pinService = new PinService( - this.accountService, - this.encryptService, - this.kdfConfigService, - this.keyGenerationService, - this.logService, - this.keyService, - this.sdkService, - pinStateService, - ); - this.appIdService = new AppIdService(this.storageService, this.logService); this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); @@ -756,16 +745,6 @@ export default class MainBackground { VaultTimeoutStringType.OnRestart, // default vault timeout ); - this.biometricsService = new BackgroundBrowserBiometricsService( - runtimeNativeMessagingBackground, - this.logService, - this.keyService, - this.biometricStateService, - this.messagingService, - this.vaultTimeoutSettingsService, - this.pinService, - ); - this.apiService = new ApiService( this.tokenService, this.platformUtilsService, @@ -849,6 +828,27 @@ export default class MainBackground { this.configService, ); + this.pinService = new PinService( + this.accountService, + this.encryptService, + this.kdfConfigService, + this.keyGenerationService, + this.logService, + this.keyService, + this.sdkService, + pinStateService, + ); + + this.biometricsService = new BackgroundBrowserBiometricsService( + runtimeNativeMessagingBackground, + this.logService, + this.keyService, + this.biometricStateService, + this.messagingService, + this.vaultTimeoutSettingsService, + this.pinService, + ); + this.passwordStrengthService = new PasswordStrengthService(); this.passwordGenerationService = legacyPasswordGenerationServiceFactory( From 637f4961bb239b671895c972ea9f6f37c2d1a978 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:23:03 +0100 Subject: [PATCH 02/13] [deps] Billing: Update braintree-web-drop-in to v1.46.0 (#14451) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> --- package-lock.json | 112 +++++++++++++++++++++------------------------- package.json | 2 +- 2 files changed, 51 insertions(+), 63 deletions(-) diff --git a/package-lock.json b/package-lock.json index 005eb38d80b..9ec580e3a5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "@nx/js": "21.6.8", "@nx/webpack": "21.6.8", "big-integer": "1.6.52", - "braintree-web-drop-in": "1.44.0", + "braintree-web-drop-in": "1.46.0", "buffer": "6.0.3", "bufferutil": "4.0.9", "chalk": "4.1.2", @@ -4798,15 +4798,15 @@ "link": true }, "node_modules/@braintree/asset-loader": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@braintree/asset-loader/-/asset-loader-2.0.1.tgz", - "integrity": "sha512-OGAoBA5MRVsr5qg0sXM6NMJbqHnYZhBudtM6WGgpQnoX42fjUYbE6Y6qFuuerD5z3lsOAjnu80DooBs1VBuh5Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@braintree/asset-loader/-/asset-loader-2.0.3.tgz", + "integrity": "sha512-uREap1j30wKRlC0mK99nNPMpEp77NtB6XixpDfFJPZHmkrmw7IB4skKe+26LZBK1H6oSainFhAyKoP7x3eyOKA==", "license": "MIT" }, "node_modules/@braintree/browser-detection": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@braintree/browser-detection/-/browser-detection-2.0.1.tgz", - "integrity": "sha512-wpRI7AXEUh6o3ILrJbpNOYE7ItfjX/S8JZP7Z5FF66ULngBGYOqE8SeLlLKXG69Nc07HtlL/6nk/h539iz9hcQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@braintree/browser-detection/-/browser-detection-2.0.2.tgz", + "integrity": "sha512-Zrv/pyodvwv/hsjsBKXKVcwHZOkx4A/5Cy2hViXtqghAhLd3483bYUIfHZJE5JKTrd018ny1FI5pN1PHFtW7vw==", "license": "MIT" }, "node_modules/@braintree/event-emitter": { @@ -4822,9 +4822,9 @@ "license": "MIT" }, "node_modules/@braintree/iframer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@braintree/iframer/-/iframer-2.0.0.tgz", - "integrity": "sha512-x1kHOyIJNDvi4P1s6pVBZhqhBa1hqDG9+yzcsCR1oNVC0LxH9CAP8bKxioT8/auY1sUyy+D8T4Vp/jv7QqSqLQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@braintree/iframer/-/iframer-2.0.1.tgz", + "integrity": "sha512-t1zJX5+f1yxHAzBJPaQT/XVMocKodUqjTE+hYvuxxWjqEZIbH8/eT5b5n767jY16mYw3+XiDkKHqcp4Pclq1wg==", "license": "MIT" }, "node_modules/@braintree/sanitize-url": { @@ -4834,9 +4834,9 @@ "license": "MIT" }, "node_modules/@braintree/uuid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@braintree/uuid/-/uuid-1.0.0.tgz", - "integrity": "sha512-AtI5hfttWSuWAgcwLUZdcZ7Fp/8jCCUf9JTs7+Xow9ditU28zuoBovqq083yph2m3SxPYb84lGjOq+cXlXBvJg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@braintree/uuid/-/uuid-1.0.1.tgz", + "integrity": "sha512-Tgu5GoODkf4oj4aLlVIapEPEfjitIHrg5ftqY6pa5Ghr4ZUA9XtZIIZ6ZPdP9x8/X0lt/FB8tRq83QuCQCwOrA==", "license": "ISC" }, "node_modules/@braintree/wrap-promise": { @@ -17762,40 +17762,40 @@ } }, "node_modules/braintree-web": { - "version": "3.113.0", - "resolved": "https://registry.npmjs.org/braintree-web/-/braintree-web-3.113.0.tgz", - "integrity": "sha512-qykYxZyld4X1tRNgXZQ3ZGzmhDGTBTRQ6Q24KaG9PuYqo+P2TVDEDOVC6tRbkx2RUIdXLv2M6WpkG7oLqEia9Q==", + "version": "3.123.2", + "resolved": "https://registry.npmjs.org/braintree-web/-/braintree-web-3.123.2.tgz", + "integrity": "sha512-N4IH75vKY67eONc0Ao4e7F+XagFW+3ok+Nfs/eOjw5D/TUt03diMAQ8woOwJghi2ql6/yjqNzZi2zE/sTWXmJg==", "license": "MIT", "dependencies": { - "@braintree/asset-loader": "2.0.1", - "@braintree/browser-detection": "2.0.1", + "@braintree/asset-loader": "2.0.3", + "@braintree/browser-detection": "2.0.2", "@braintree/event-emitter": "0.4.1", "@braintree/extended-promise": "1.0.0", - "@braintree/iframer": "2.0.0", + "@braintree/iframer": "2.0.1", "@braintree/sanitize-url": "7.0.4", - "@braintree/uuid": "1.0.0", + "@braintree/uuid": "1.0.1", "@braintree/wrap-promise": "2.1.0", "@paypal/accelerated-checkout-loader": "1.1.0", - "card-validator": "10.0.0", - "credit-card-type": "10.0.1", - "framebus": "6.0.0", - "inject-stylesheet": "6.0.1", + "card-validator": "10.0.3", + "credit-card-type": "10.0.2", + "framebus": "6.0.3", + "inject-stylesheet": "6.0.2", "promise-polyfill": "8.2.3", - "restricted-input": "3.0.5" + "restricted-input": "4.0.3" } }, "node_modules/braintree-web-drop-in": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/braintree-web-drop-in/-/braintree-web-drop-in-1.44.0.tgz", - "integrity": "sha512-maOq9SwiXztIzixJhOras7K44x4UIqqnkyQMYAJqxQ8WkADv9AkflCu2j3IeVYCus/Th9gWWFHcBugn3C4sZGw==", + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/braintree-web-drop-in/-/braintree-web-drop-in-1.46.0.tgz", + "integrity": "sha512-KxCjJpaigoMajYD/iIA+ohXaI6Olt2Bj/Yu45WpJOjolKO9n1UmXl9bsq9UIiGOFIGqi/JWva1wI4cIHHvcI1A==", "license": "MIT", "dependencies": { - "@braintree/asset-loader": "2.0.1", - "@braintree/browser-detection": "2.0.1", + "@braintree/asset-loader": "2.0.3", + "@braintree/browser-detection": "2.0.2", "@braintree/event-emitter": "0.4.1", - "@braintree/uuid": "1.0.0", + "@braintree/uuid": "1.0.1", "@braintree/wrap-promise": "2.1.0", - "braintree-web": "3.113.0" + "braintree-web": "3.123.2" } }, "node_modules/browser-assert": { @@ -18444,20 +18444,14 @@ "license": "CC-BY-4.0" }, "node_modules/card-validator": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/card-validator/-/card-validator-10.0.0.tgz", - "integrity": "sha512-2fLyCBOxO7/b56sxoYav8FeJqv9bWpZSyKq8sXKxnpxTGXHnM/0c8WEKG+ZJ+OXFcabnl98pD0EKBtTn+Tql0g==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/card-validator/-/card-validator-10.0.3.tgz", + "integrity": "sha512-xOEDsK3hojV0OIpmrR64eZGpngnOqRDEP20O+WSRtvjLSW6nyekW4i2N9SzYg679uFO3RyHcFHxb+mml5tXc4A==", "license": "MIT", "dependencies": { - "credit-card-type": "^9.1.0" + "credit-card-type": "^10.0.2" } }, - "node_modules/card-validator/node_modules/credit-card-type": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/credit-card-type/-/credit-card-type-9.1.0.tgz", - "integrity": "sha512-CpNFuLxiPFxuZqhSKml3M+t0K/484pMAnfYWH14JoD7OZMnmC0Lmo+P7JX9SobqFpRoo7ifA18kOHdxJywYPEA==", - "license": "MIT" - }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -19692,9 +19686,9 @@ "license": "MIT" }, "node_modules/credit-card-type": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/credit-card-type/-/credit-card-type-10.0.1.tgz", - "integrity": "sha512-vQOuWmBgsgG1ovGeDi8m6Zeu1JaqH/JncrxKmaqMbv/LunyOQdLiQhPHtOsNlbUI05TocR5nod/Mbs3HYtr6sQ==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/credit-card-type/-/credit-card-type-10.0.2.tgz", + "integrity": "sha512-vt/iQokU0mtrT7ceRU75FSmWnIh5JFpLsUUUWYRmztYekOGm0ZbCuzwFTbNkq41k92y+0B8ChscFhRN9DhVZEA==", "license": "MIT" }, "node_modules/cross-dirname": { @@ -23410,20 +23404,14 @@ } }, "node_modules/framebus": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/framebus/-/framebus-6.0.0.tgz", - "integrity": "sha512-bL9V68hVaVBCY9rveoWbPFFI9hAXIJtESs51B+9XmzvMt38+wP8b4VdiJsavjMS6NfPZ/afQ/jc2qaHmSGI1kQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/framebus/-/framebus-6.0.3.tgz", + "integrity": "sha512-G/N2p+kFZ1xPBge7tbtTq2KcTR1kSKs1rVbTqH//WdtvJSexS33fsTTOq3yfUWvUczqhujyaFc+omawC9YyRBg==", "license": "MIT", "dependencies": { - "@braintree/uuid": "^0.1.0" + "@braintree/uuid": "^1.0.0" } }, - "node_modules/framebus/node_modules/@braintree/uuid": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@braintree/uuid/-/uuid-0.1.0.tgz", - "integrity": "sha512-YvZJdlNcK5EnR+7M8AjgEAf4Qx696+FOSYlPfy5ePn80vODtVAUU0FxHnzKZC0og1VbDNQDDiwhthR65D4Na0g==", - "license": "ISC" - }, "node_modules/fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", @@ -24987,9 +24975,9 @@ } }, "node_modules/inject-stylesheet": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/inject-stylesheet/-/inject-stylesheet-6.0.1.tgz", - "integrity": "sha512-2fvune1D4+8mvJoLVo95ncY4HrDkIaYIReRzXv8tkWFgdG9iuc5QuX57gtSDPWTWQI/f5BGwwtH85wxHouzucg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/inject-stylesheet/-/inject-stylesheet-6.0.2.tgz", + "integrity": "sha512-sswMueya1LXEfwcy7KXPuq3zAW6HvgAeViApEhIaCviCkP4XYoKrQj8ftEmxPmIHn88X4R3xOAsnN/QCPvVKWw==", "license": "MIT" }, "node_modules/inquirer": { @@ -36154,12 +36142,12 @@ "license": "ISC" }, "node_modules/restricted-input": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/restricted-input/-/restricted-input-3.0.5.tgz", - "integrity": "sha512-lUuXZ3wUnHURRarj5/0C8vomWIfWJO+p7T6RYwB46v7Oyuyr3yyupU+i7SjqUv4S6RAeAAZt1C/QCLJ9xhQBow==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/restricted-input/-/restricted-input-4.0.3.tgz", + "integrity": "sha512-VpkwT5Fr3DhvoRZfPnmHDhnYAYETjjNzDlvA4NlW0iknFS47C5X4OCHEpOOxaPjvmka5V8d1ty1jVVoorZKvHg==", "license": "MIT", "dependencies": { - "@braintree/browser-detection": "^1.12.1" + "@braintree/browser-detection": "^1.17.2" } }, "node_modules/restricted-input/node_modules/@braintree/browser-detection": { diff --git a/package.json b/package.json index 7ca866b3c4d..87a78a30796 100644 --- a/package.json +++ b/package.json @@ -175,7 +175,7 @@ "@nx/js": "21.6.8", "@nx/webpack": "21.6.8", "big-integer": "1.6.52", - "braintree-web-drop-in": "1.44.0", + "braintree-web-drop-in": "1.46.0", "buffer": "6.0.3", "bufferutil": "4.0.9", "chalk": "4.1.2", From 7e32d0a59faa97a897cc3c47e98403396425d360 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 24 Nov 2025 16:36:23 +0100 Subject: [PATCH 03/13] [PM-27564] Self-host configuration is not applied with nx build (#17279) * fix: web not using env variables * fix: apply claude suggestion * fix: remove non-working serve targets --- apps/web/project.json | 30 --------------------- apps/web/webpack.base.js | 14 +++++++--- apps/web/webpack.config.js | 1 + bitwarden_license/bit-web/webpack.config.js | 2 ++ 4 files changed, 13 insertions(+), 34 deletions(-) diff --git a/apps/web/project.json b/apps/web/project.json index 4f51bf22740..710fd7cb5e7 100644 --- a/apps/web/project.json +++ b/apps/web/project.json @@ -154,45 +154,15 @@ }, "configurations": { "oss": { - "buildTarget": "web:build:oss" - }, - "oss-dev": { "buildTarget": "web:build:oss-dev" }, "commercial": { - "buildTarget": "web:build:commercial" - }, - "commercial-dev": { "buildTarget": "web:build:commercial-dev" }, - "commercial-qa": { - "buildTarget": "web:build:commercial-qa" - }, - "commercial-cloud": { - "buildTarget": "web:build:commercial-cloud" - }, - "commercial-euprd": { - "buildTarget": "web:build:commercial-euprd" - }, - "commercial-euqa": { - "buildTarget": "web:build:commercial-euqa" - }, - "commercial-usdev": { - "buildTarget": "web:build:commercial-usdev" - }, - "commercial-ee": { - "buildTarget": "web:build:commercial-ee" - }, "oss-selfhost": { - "buildTarget": "web:build:oss-selfhost" - }, - "oss-selfhost-dev": { "buildTarget": "web:build:oss-selfhost-dev" }, "commercial-selfhost": { - "buildTarget": "web:build:commercial-selfhost" - }, - "commercial-selfhost-dev": { "buildTarget": "web:build:commercial-selfhost-dev" } } diff --git a/apps/web/webpack.base.js b/apps/web/webpack.base.js index f1e627a58a8..cc17b3b7cfd 100644 --- a/apps/web/webpack.base.js +++ b/apps/web/webpack.base.js @@ -13,9 +13,11 @@ const config = require(path.resolve(__dirname, "config.js")); const pjson = require(path.resolve(__dirname, "package.json")); module.exports.getEnv = function getEnv(params) { - const ENV = params.env || (process.env.ENV == null ? "development" : process.env.ENV); - const NODE_ENV = process.env.NODE_ENV == null ? "development" : process.env.NODE_ENV; - const LOGGING = process.env.LOGGING != "false"; + const ENV = params.env?.ENV ?? process.env?.ENV ?? "development"; + const NODE_ENV = params.env?.NODE_ENV ?? process.env?.NODE_ENV ?? "development"; + const LOGGING = + params.env?.LOGGING ?? + (process.env?.LOGGING === undefined ? true : process.env.LOGGING !== "false"); return { ENV, NODE_ENV, LOGGING }; }; @@ -35,7 +37,11 @@ const DEFAULT_PARAMS = { * tsConfig: string; * outputPath?: string; * mode?: string; - * env?: string; + * env?: { + * ENV?: string; + * NODE_ENV?: string; + * LOGGING?: boolean; + * }; * importAliases?: import("webpack").ResolveOptions["alias"]; * }} params */ diff --git a/apps/web/webpack.config.js b/apps/web/webpack.config.js index 962d72ac825..275a6a5f3b0 100644 --- a/apps/web/webpack.config.js +++ b/apps/web/webpack.config.js @@ -15,6 +15,7 @@ module.exports = (webpackConfig, context) => { }, tsConfig: "apps/web/tsconfig.build.json", outputPath: path.resolve(context.context.root, context.options.outputPath), + env: context.options.env, }); } else { return buildConfig({ diff --git a/bitwarden_license/bit-web/webpack.config.js b/bitwarden_license/bit-web/webpack.config.js index 6433eee59f6..8ab719072f6 100644 --- a/bitwarden_license/bit-web/webpack.config.js +++ b/bitwarden_license/bit-web/webpack.config.js @@ -3,6 +3,7 @@ const { buildConfig } = require(path.resolve(__dirname, "../../apps/web/webpack. module.exports = (webpackConfig, context) => { const isNxBuild = context && context.options; + if (isNxBuild) { return buildConfig({ configName: "Commercial", @@ -23,6 +24,7 @@ module.exports = (webpackConfig, context) => { alias: "@bitwarden/commercial-sdk-internal", }, ], + env: context.options.env, }); } else { return buildConfig({ From 3a4eec38a1b1115601b350e0a3387e7448e650cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 15:38:40 +0000 Subject: [PATCH 04/13] [deps] Platform: Update Rust crate arboard to v3.6.1 (#17547) 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/desktop/desktop_native/Cargo.lock | 5 +++-- apps/desktop/desktop_native/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 475253f935f..a1cdb9f26eb 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.6.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55f533f8e0af236ffe5eb979b99381df3258853f00ba2e44b6e1955292c75227" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" dependencies = [ "clipboard-win", "log", @@ -131,6 +131,7 @@ dependencies = [ "objc2-foundation", "parking_lot", "percent-encoding", + "windows-sys 0.60.2", "wl-clipboard-rs", "x11rb", ] diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 864b743962d..0b09daa9bdd 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -22,7 +22,7 @@ publish = false aes = "=0.8.4" aes-gcm = "=0.10.3" anyhow = "=1.0.94" -arboard = { version = "=3.6.0", default-features = false } +arboard = { version = "=3.6.1", default-features = false } ashpd = "=0.11.0" base64 = "=0.22.1" bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "a641316227227f8777fdf56ac9fa2d6b5f7fe662" } From 5779df241721634ffbf78a4229272bab4eb15e01 Mon Sep 17 00:00:00 2001 From: Leslie Tilton <23057410+Banrion@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:46:28 -0600 Subject: [PATCH 05/13] Correct phishing blocker file structure (#17477) --- .../{pages => popup}/phishing-warning.component.html | 0 .../{pages => popup}/phishing-warning.component.ts | 3 --- .../{pages => popup}/phishing-warning.stories.ts | 2 -- .../{pages => popup}/protected-by-component.html | 0 .../{pages => popup}/protected-by-component.ts | 2 -- apps/browser/src/popup/app-routing.module.ts | 4 ++-- 6 files changed, 2 insertions(+), 9 deletions(-) rename apps/browser/src/dirt/phishing-detection/{pages => popup}/phishing-warning.component.html (100%) rename apps/browser/src/dirt/phishing-detection/{pages => popup}/phishing-warning.component.ts (93%) rename apps/browser/src/dirt/phishing-detection/{pages => popup}/phishing-warning.stories.ts (97%) rename apps/browser/src/dirt/phishing-detection/{pages => popup}/protected-by-component.html (100%) rename apps/browser/src/dirt/phishing-detection/{pages => popup}/protected-by-component.ts (86%) diff --git a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.html b/apps/browser/src/dirt/phishing-detection/popup/phishing-warning.component.html similarity index 100% rename from apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.html rename to apps/browser/src/dirt/phishing-detection/popup/phishing-warning.component.html diff --git a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.ts b/apps/browser/src/dirt/phishing-detection/popup/phishing-warning.component.ts similarity index 93% rename from apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.ts rename to apps/browser/src/dirt/phishing-detection/popup/phishing-warning.component.ts index 589b880b206..d8e9895237c 100644 --- a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.ts +++ b/apps/browser/src/dirt/phishing-detection/popup/phishing-warning.component.ts @@ -1,8 +1,5 @@ -// eslint-disable-next-line no-restricted-imports import { CommonModule } from "@angular/common"; -// eslint-disable-next-line no-restricted-imports import { Component, inject } from "@angular/core"; -// eslint-disable-next-line no-restricted-imports import { ActivatedRoute, RouterModule } from "@angular/router"; import { firstValueFrom, map } from "rxjs"; diff --git a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.stories.ts b/apps/browser/src/dirt/phishing-detection/popup/phishing-warning.stories.ts similarity index 97% rename from apps/browser/src/dirt/phishing-detection/pages/phishing-warning.stories.ts rename to apps/browser/src/dirt/phishing-detection/popup/phishing-warning.stories.ts index e79543605c2..32b3c102c36 100644 --- a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.stories.ts +++ b/apps/browser/src/dirt/phishing-detection/popup/phishing-warning.stories.ts @@ -1,5 +1,3 @@ -// TODO: This needs to be dealt with by moving this folder or updating the lint rule. -/* eslint-disable no-restricted-imports */ import { ActivatedRoute, RouterModule } from "@angular/router"; import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { BehaviorSubject, of } from "rxjs"; diff --git a/apps/browser/src/dirt/phishing-detection/pages/protected-by-component.html b/apps/browser/src/dirt/phishing-detection/popup/protected-by-component.html similarity index 100% rename from apps/browser/src/dirt/phishing-detection/pages/protected-by-component.html rename to apps/browser/src/dirt/phishing-detection/popup/protected-by-component.html diff --git a/apps/browser/src/dirt/phishing-detection/pages/protected-by-component.ts b/apps/browser/src/dirt/phishing-detection/popup/protected-by-component.ts similarity index 86% rename from apps/browser/src/dirt/phishing-detection/pages/protected-by-component.ts rename to apps/browser/src/dirt/phishing-detection/popup/protected-by-component.ts index 71cdac89aa2..8da916af5e6 100644 --- a/apps/browser/src/dirt/phishing-detection/pages/protected-by-component.ts +++ b/apps/browser/src/dirt/phishing-detection/popup/protected-by-component.ts @@ -1,6 +1,4 @@ -// eslint-disable-next-line no-restricted-imports import { CommonModule } from "@angular/common"; -// eslint-disable-next-line no-restricted-imports import { Component } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 1834beb391e..a36396afa1a 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -56,8 +56,8 @@ import { BlockedDomainsComponent } from "../autofill/popup/settings/blocked-doma import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; import { PremiumV2Component } from "../billing/popup/settings/premium-v2.component"; -import { PhishingWarning } from "../dirt/phishing-detection/pages/phishing-warning.component"; -import { ProtectedByComponent } from "../dirt/phishing-detection/pages/protected-by-component"; +import { PhishingWarning } from "../dirt/phishing-detection/popup/phishing-warning.component"; +import { ProtectedByComponent } from "../dirt/phishing-detection/popup/protected-by-component"; import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import BrowserPopupUtils from "../platform/browser/browser-popup-utils"; import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router-cache.service"; From 4c36a46ef27e0d5d3c91595d47ebdcad965a80b6 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 24 Nov 2025 18:03:16 +0100 Subject: [PATCH 06/13] Enable directive-class-suffix (#17385) --- .../navigation-switcher/navigation-switcher.stories.ts | 4 ++++ .../layouts/product-switcher/product-switcher.stories.ts | 4 ++++ eslint.config.mjs | 2 +- .../src/auth/components/user-verification.component.ts | 2 ++ .../src/directives/cipherListVirtualScroll.directive.ts | 2 ++ libs/components/src/form-control/form-control.module.ts | 6 +++--- .../form-control/{hint.component.ts => hint.directive.ts} | 2 +- libs/components/src/form-field/form-field.component.ts | 4 ++-- libs/components/src/switch/switch.component.ts | 4 ++-- libs/components/src/table/table-scroll.component.ts | 4 ++-- libs/components/src/table/table.module.ts | 6 +++--- libs/vault/src/directives/readonly-textarea.directive.ts | 2 ++ 12 files changed, 28 insertions(+), 14 deletions(-) rename libs/components/src/form-control/{hint.component.ts => hint.directive.ts} (90%) 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 faf1b796b00..88132e56384 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 @@ -30,6 +30,8 @@ import { NavigationProductSwitcherComponent } from "./navigation-switcher.compon selector: "[mockOrgs]", standalone: false, }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28232): Use Directive suffix +// eslint-disable-next-line @angular-eslint/directive-class-suffix class MockOrganizationService implements Partial { private static _orgs = new BehaviorSubject([]); @@ -49,6 +51,8 @@ class MockOrganizationService implements Partial { selector: "[mockProviders]", standalone: false, }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28232): Use Directive suffix +// eslint-disable-next-line @angular-eslint/directive-class-suffix class MockProviderService implements Partial { private static _providers = new BehaviorSubject([]); 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 4c6af713464..4581f5981e6 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 @@ -30,6 +30,8 @@ import { ProductSwitcherService } from "./shared/product-switcher.service"; selector: "[mockOrgs]", standalone: false, }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28232): Use Directive suffix +// eslint-disable-next-line @angular-eslint/directive-class-suffix class MockOrganizationService implements Partial { private static _orgs = new BehaviorSubject([]); @@ -49,6 +51,8 @@ class MockOrganizationService implements Partial { selector: "[mockProviders]", standalone: false, }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28232): Use Directive suffix +// eslint-disable-next-line @angular-eslint/directive-class-suffix class MockProviderService implements Partial { private static _providers = new BehaviorSubject([]); diff --git a/eslint.config.mjs b/eslint.config.mjs index 7f9bb2284f7..1e12e0e1e19 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -63,7 +63,7 @@ export default tseslint.config( // TODO: Enable these. "@angular-eslint/component-class-suffix": "error", "@angular-eslint/contextual-lifecycle": "error", - "@angular-eslint/directive-class-suffix": 0, + "@angular-eslint/directive-class-suffix": "error", "@angular-eslint/no-empty-lifecycle-method": 0, "@angular-eslint/no-input-rename": 0, "@angular-eslint/no-inputs-metadata-property": "error", diff --git a/libs/angular/src/auth/components/user-verification.component.ts b/libs/angular/src/auth/components/user-verification.component.ts index 1f0659a92ff..a2cee2f1099 100644 --- a/libs/angular/src/auth/components/user-verification.component.ts +++ b/libs/angular/src/auth/components/user-verification.component.ts @@ -24,6 +24,8 @@ import { KeyService } from "@bitwarden/key-management"; selector: "app-user-verification", standalone: false, }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28232): Use Directive suffix +// eslint-disable-next-line @angular-eslint/directive-class-suffix export class UserVerificationComponent implements ControlValueAccessor, OnInit, OnDestroy { private _invalidSecret = false; // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals diff --git a/libs/angular/src/directives/cipherListVirtualScroll.directive.ts b/libs/angular/src/directives/cipherListVirtualScroll.directive.ts index 442e01c1c79..8e7b5cc204d 100644 --- a/libs/angular/src/directives/cipherListVirtualScroll.directive.ts +++ b/libs/angular/src/directives/cipherListVirtualScroll.directive.ts @@ -45,6 +45,8 @@ export function _cipherListVirtualScrollStrategyFactory(cipherListDir: CipherLis }, ], }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28232): Use Directive suffix +// eslint-disable-next-line @angular-eslint/directive-class-suffix export class CipherListVirtualScroll extends CdkFixedSizeVirtualScroll { _scrollStrategy: CipherListVirtualScrollStrategy; diff --git a/libs/components/src/form-control/form-control.module.ts b/libs/components/src/form-control/form-control.module.ts index 2646f36ecd3..d87284adbcd 100644 --- a/libs/components/src/form-control/form-control.module.ts +++ b/libs/components/src/form-control/form-control.module.ts @@ -1,11 +1,11 @@ import { NgModule } from "@angular/core"; import { FormControlComponent } from "./form-control.component"; -import { BitHintComponent } from "./hint.component"; +import { BitHintDirective } from "./hint.directive"; import { BitLabelComponent } from "./label.component"; @NgModule({ - imports: [BitLabelComponent, FormControlComponent, BitHintComponent], - exports: [FormControlComponent, BitLabelComponent, BitHintComponent], + imports: [BitLabelComponent, FormControlComponent, BitHintDirective], + exports: [FormControlComponent, BitLabelComponent, BitHintDirective], }) export class FormControlModule {} diff --git a/libs/components/src/form-control/hint.component.ts b/libs/components/src/form-control/hint.directive.ts similarity index 90% rename from libs/components/src/form-control/hint.component.ts rename to libs/components/src/form-control/hint.directive.ts index c1f21bf2545..110aefc30e7 100644 --- a/libs/components/src/form-control/hint.component.ts +++ b/libs/components/src/form-control/hint.directive.ts @@ -9,6 +9,6 @@ let nextId = 0; class: "tw-text-muted tw-font-normal tw-inline-block tw-mt-1 tw-text-xs", }, }) -export class BitHintComponent { +export class BitHintDirective { @HostBinding() id = `bit-hint-${nextId++}`; } diff --git a/libs/components/src/form-field/form-field.component.ts b/libs/components/src/form-field/form-field.component.ts index 3d49a58b1fc..10cf33b8257 100644 --- a/libs/components/src/form-field/form-field.component.ts +++ b/libs/components/src/form-field/form-field.component.ts @@ -15,7 +15,7 @@ import { import { I18nPipe } from "@bitwarden/ui-common"; -import { BitHintComponent } from "../form-control/hint.component"; +import { BitHintDirective } from "../form-control/hint.directive"; import { BitLabelComponent } from "../form-control/label.component"; import { inputBorderClasses } from "../input/input.directive"; @@ -31,7 +31,7 @@ import { BitFormFieldControl } from "./form-field-control"; }) export class BitFormFieldComponent implements AfterContentChecked { readonly input = contentChild.required(BitFormFieldControl); - readonly hint = contentChild(BitHintComponent); + readonly hint = contentChild(BitHintDirective); readonly label = contentChild(BitLabelComponent); readonly prefixContainer = viewChild>("prefixContainer"); diff --git a/libs/components/src/switch/switch.component.ts b/libs/components/src/switch/switch.component.ts index 30e1ac59d48..a93e274e8bb 100644 --- a/libs/components/src/switch/switch.component.ts +++ b/libs/components/src/switch/switch.component.ts @@ -13,7 +13,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; import { AriaDisableDirective } from "../a11y"; import { FormControlModule } from "../form-control/form-control.module"; -import { BitHintComponent } from "../form-control/hint.component"; +import { BitHintDirective } from "../form-control/hint.directive"; import { BitLabelComponent } from "../form-control/label.component"; let nextId = 0; @@ -56,7 +56,7 @@ export class SwitchComponent implements ControlValueAccessor, AfterViewInit { protected readonly disabled = model(false); protected readonly disabledReasonText = input(null); - private readonly hintComponent = contentChild(BitHintComponent); + private readonly hintComponent = contentChild(BitHintDirective); protected readonly disabledReasonTextId = `bit-switch-disabled-text-${nextId++}`; diff --git a/libs/components/src/table/table-scroll.component.ts b/libs/components/src/table/table-scroll.component.ts index fcdd401a1a2..1ccb49a85e5 100644 --- a/libs/components/src/table/table-scroll.component.ts +++ b/libs/components/src/table/table-scroll.component.ts @@ -35,7 +35,7 @@ import { TableComponent } from "./table.component"; @Directive({ selector: "[bitRowDef]", }) -export class BitRowDef { +export class BitRowDefDirective { constructor(public template: TemplateRef) {} } @@ -69,7 +69,7 @@ export class TableScrollComponent /** Optional trackBy function. */ readonly trackBy = input | undefined>(); - protected readonly rowDef = contentChild(BitRowDef); + protected readonly rowDef = contentChild(BitRowDefDirective); /** * Height of the thead element (in pixels). diff --git a/libs/components/src/table/table.module.ts b/libs/components/src/table/table.module.ts index 68993612772..5e44f604481 100644 --- a/libs/components/src/table/table.module.ts +++ b/libs/components/src/table/table.module.ts @@ -5,14 +5,14 @@ import { NgModule } from "@angular/core"; import { CellDirective } from "./cell.directive"; import { RowDirective } from "./row.directive"; import { SortableComponent } from "./sortable.component"; -import { BitRowDef, TableScrollComponent } from "./table-scroll.component"; +import { BitRowDefDirective, TableScrollComponent } from "./table-scroll.component"; import { TableBodyDirective, TableComponent } from "./table.component"; @NgModule({ imports: [ CommonModule, ScrollingModule, - BitRowDef, + BitRowDefDirective, CellDirective, RowDirective, SortableComponent, @@ -21,7 +21,7 @@ import { TableBodyDirective, TableComponent } from "./table.component"; TableScrollComponent, ], exports: [ - BitRowDef, + BitRowDefDirective, CellDirective, RowDirective, SortableComponent, diff --git a/libs/vault/src/directives/readonly-textarea.directive.ts b/libs/vault/src/directives/readonly-textarea.directive.ts index 65bd9d6e353..17c5f865fb3 100644 --- a/libs/vault/src/directives/readonly-textarea.directive.ts +++ b/libs/vault/src/directives/readonly-textarea.directive.ts @@ -8,6 +8,8 @@ import { firstValueFrom } from "rxjs"; providers: [TextFieldModule], hostDirectives: [CdkTextareaAutosize], }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28232): Use Directive suffix +// eslint-disable-next-line @angular-eslint/directive-class-suffix export class VaultAutosizeReadOnlyTextArea implements AfterViewInit { constructor( @Host() private autosize: CdkTextareaAutosize, From 613e0c546143ef9d091380acb69cab94627c5627 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham Date: Mon, 24 Nov 2025 13:08:25 -0500 Subject: [PATCH 07/13] [CL-925] add filled danger button (#17633) * add dangerPrimary button variant * add dangerPrimary to small story --- libs/components/src/button/button.component.ts | 8 ++++++++ libs/components/src/button/button.stories.ts | 8 ++++++++ libs/components/src/shared/button-like.abstraction.ts | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/libs/components/src/button/button.component.ts b/libs/components/src/button/button.component.ts index 0e50ccbe87a..7cae8fe974d 100644 --- a/libs/components/src/button/button.component.ts +++ b/libs/components/src/button/button.component.ts @@ -54,6 +54,14 @@ const buttonStyles: Record = { "hover:!tw-text-contrast", ...focusRing, ], + dangerPrimary: [ + "tw-border-danger-600", + "tw-bg-danger-600", + "!tw-text-contrast", + "hover:tw-bg-danger-700", + "hover:tw-border-danger-700", + ...focusRing, + ], unstyled: [], }; diff --git a/libs/components/src/button/button.stories.ts b/libs/components/src/button/button.stories.ts index 7319b47bce5..29c4dea3088 100644 --- a/libs/components/src/button/button.stories.ts +++ b/libs/components/src/button/button.stories.ts @@ -62,6 +62,13 @@ export const Primary: Story = { }, }; +export const DangerPrimary: Story = { + ...Default, + args: { + buttonType: "dangerPrimary", + }, +}; + export const Danger: Story = { ...Default, args: { @@ -77,6 +84,7 @@ export const Small: Story = { + `, }), diff --git a/libs/components/src/shared/button-like.abstraction.ts b/libs/components/src/shared/button-like.abstraction.ts index 63391743837..45a661b6ecb 100644 --- a/libs/components/src/shared/button-like.abstraction.ts +++ b/libs/components/src/shared/button-like.abstraction.ts @@ -1,6 +1,6 @@ import { ModelSignal } from "@angular/core"; -export type ButtonType = "primary" | "secondary" | "danger" | "unstyled"; +export type ButtonType = "primary" | "secondary" | "danger" | "dangerPrimary" | "unstyled"; export type ButtonSize = "default" | "small"; From 883ff8968e366edeac319f1531d97a30c2c2b6cc Mon Sep 17 00:00:00 2001 From: blackwood Date: Mon, 24 Nov 2025 14:08:11 -0500 Subject: [PATCH 08/13] Allows limited internal message posting when host experience content is controlled (#17313) --- .../src/background/runtime.background.ts | 66 ++++++++++++++++--- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 798a7583f85..597babdc777 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -293,14 +293,24 @@ export default class RuntimeBackground { case "openPopup": await this.openPopup(); break; - case VaultMessages.OpenAtRiskPasswords: + case VaultMessages.OpenAtRiskPasswords: { + if (await this.shouldRejectManyOriginMessage(msg)) { + return; + } + await this.main.openAtRisksPasswordsPage(); this.announcePopupOpen(); break; - case VaultMessages.OpenBrowserExtensionToUrl: + } + case VaultMessages.OpenBrowserExtensionToUrl: { + if (await this.shouldRejectManyOriginMessage(msg)) { + return; + } + await this.main.openTheExtensionToPage(msg.url); this.announcePopupOpen(); break; + } case "bgUpdateContextMenu": case "editedCipher": case "addedCipher": @@ -312,10 +322,7 @@ export default class RuntimeBackground { break; } case "authResult": { - const env = await firstValueFrom(this.environmentService.environment$); - const vaultUrl = env.getWebVaultUrl(); - - if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) { + if (!(await this.isValidVaultReferrer(msg.referrer))) { return; } @@ -334,10 +341,7 @@ export default class RuntimeBackground { break; } case "webAuthnResult": { - const env = await firstValueFrom(this.environmentService.environment$); - const vaultUrl = env.getWebVaultUrl(); - - if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) { + if (!(await this.isValidVaultReferrer(msg.referrer))) { return; } @@ -372,6 +376,48 @@ export default class RuntimeBackground { } } + /** + * For messages that can originate from a vault host page or extension, validate referrer or external + * + * @param message + * @returns true if message fails validation + */ + private async shouldRejectManyOriginMessage(message: { + webExtSender: chrome.runtime.MessageSender; + }): Promise { + const isValidVaultReferrer = await this.isValidVaultReferrer( + Utils.getHostname(message?.webExtSender?.origin), + ); + + if (isValidVaultReferrer) { + return false; + } + + return isExternalMessage(message); + } + + /** + * Validates a message's referrer matches the configured web vault hostname. + * + * @param referrer - hostname from message source + * @returns true if referrer matches web vault + */ + private async isValidVaultReferrer(referrer: string | null | undefined): Promise { + if (!referrer) { + return false; + } + + const env = await firstValueFrom(this.environmentService.environment$); + const vaultUrl = env.getWebVaultUrl(); + const vaultHostname = Utils.getHostname(vaultUrl); + + if (!vaultHostname) { + return false; + } + + return vaultHostname === referrer; + } + private async autofillPage(tabToAutoFill: chrome.tabs.Tab) { const totpCode = await this.autofillService.doAutoFill({ tab: tabToAutoFill, From 43fd99b002eefe3571dcc9e86e97b3a4fd264448 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:49:05 -0800 Subject: [PATCH 09/13] [PM-24722][PM-27695] - add persistent callout in settings for non-premium users (#17246) * add persistent callout in settings for non-premium users * remove premium v2 component * add spec * remove premium-v2.component.html * fix title * fix typo * conditionally render h2 * re-add pemiumv2component. change class prop to observable * change from bold to semibold * remove unecessary tw classes. use transform: booleanAttribute * add spotlight specs * code cleanup --- apps/browser/src/_locales/en/messages.json | 3 + .../popup/settings/settings-v2.component.html | 17 +- .../settings/settings-v2.component.spec.ts | 260 ++++++++++++++++++ .../popup/settings/settings-v2.component.ts | 43 ++- ...more-from-bitwarden-page-v2.component.html | 6 - .../more-from-bitwarden-page-v2.component.ts | 12 +- .../spotlight/spotlight.component.html | 16 +- .../spotlight/spotlight.component.spec.ts | 208 ++++++++++++++ .../spotlight/spotlight.component.ts | 35 +-- 9 files changed, 537 insertions(+), 63 deletions(-) create mode 100644 apps/browser/src/tools/popup/settings/settings-v2.component.spec.ts create mode 100644 libs/angular/src/vault/components/spotlight/spotlight.component.spec.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 5cc7c30bfb4..14915175da1 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -4902,6 +4902,9 @@ "premium": { "message": "Premium" }, + "unlockFeaturesWithPremium": { + "message": "Unlock reporting, emergency access, and more security features with Premium." + }, "freeOrgsCannotUseAttachments": { "message": "Free organizations cannot use attachments" }, 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 a12c5fe005f..683b7d70ed6 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.html +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.html @@ -1,4 +1,19 @@ + + {{ "unlockFeaturesWithPremium" | i18n }} + + + @@ -20,7 +35,7 @@

{{ "autofill" | i18n }}

{ + let account$: BehaviorSubject; + let mockAccountService: Partial; + let mockBillingState: { hasPremiumFromAnySource$: jest.Mock }; + let mockNudges: { + showNudgeBadge$: jest.Mock; + dismissNudge: jest.Mock; + }; + let mockAutofillSettings: { + defaultBrowserAutofillDisabled$: Subject; + isBrowserAutofillSettingOverridden: jest.Mock>; + }; + let dialogService: MockProxy; + let openSpy: jest.SpyInstance; + + beforeEach(waitForAsync(async () => { + dialogService = mock(); + account$ = new BehaviorSubject(null); + mockAccountService = { + activeAccount$: account$ as unknown as AccountService["activeAccount$"], + }; + + mockBillingState = { + hasPremiumFromAnySource$: jest.fn().mockReturnValue(of(false)), + }; + + mockNudges = { + showNudgeBadge$: jest.fn().mockImplementation(() => of(false)), + dismissNudge: jest.fn().mockResolvedValue(undefined), + }; + + mockAutofillSettings = { + defaultBrowserAutofillDisabled$: new BehaviorSubject(false), + isBrowserAutofillSettingOverridden: jest.fn().mockResolvedValue(false), + }; + + jest.spyOn(BrowserApi, "getBrowserClientVendor").mockReturnValue("Chrome"); + + const cfg = TestBed.configureTestingModule({ + imports: [SettingsV2Component, RouterTestingModule], + providers: [ + { provide: AccountService, useValue: mockAccountService }, + { provide: BillingAccountProfileStateService, useValue: mockBillingState }, + { provide: NudgesService, useValue: mockNudges }, + { provide: AutofillBrowserSettingsService, useValue: mockAutofillSettings }, + { provide: DialogService, useValue: dialogService }, + { provide: I18nService, useValue: { t: jest.fn((key: string) => key) } }, + { provide: GlobalStateProvider, useValue: new FakeGlobalStateProvider() }, + { provide: PlatformUtilsService, useValue: mock() }, + { provide: AvatarService, useValue: mock() }, + { provide: AuthService, useValue: mock() }, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + }); + + TestBed.overrideComponent(SettingsV2Component, { + add: { + imports: [CurrentAccountStubComponent], + providers: [{ provide: DialogService, useValue: dialogService }], + }, + remove: { + imports: [CurrentAccountComponent], + }, + }); + + await cfg.compileComponents(); + })); + + afterEach(() => { + jest.resetAllMocks(); + }); + + function pushActiveAccount(id = "user-123"): Account { + const acct = { id } as Account; + account$.next(acct); + return acct; + } + + it("shows the premium spotlight when user does NOT have premium", async () => { + mockBillingState.hasPremiumFromAnySource$.mockReturnValue(of(false)); + pushActiveAccount(); + + const fixture = TestBed.createComponent(SettingsV2Component); + fixture.detectChanges(); + await fixture.whenStable(); + + const el: HTMLElement = fixture.nativeElement; + + expect(el.querySelector("bit-spotlight")).toBeTruthy(); + }); + + it("hides the premium spotlight when user HAS premium", async () => { + mockBillingState.hasPremiumFromAnySource$.mockReturnValue(of(true)); + pushActiveAccount(); + + const fixture = TestBed.createComponent(SettingsV2Component); + fixture.detectChanges(); + await fixture.whenStable(); + + const el: HTMLElement = fixture.nativeElement; + expect(el.querySelector("bit-spotlight")).toBeFalsy(); + }); + + it("openUpgradeDialog calls PremiumUpgradeDialogComponent.open with the DialogService", async () => { + openSpy = jest.spyOn(PremiumUpgradeDialogComponent, "open").mockImplementation(); + mockBillingState.hasPremiumFromAnySource$.mockReturnValue(of(false)); + pushActiveAccount(); + + const fixture = TestBed.createComponent(SettingsV2Component); + const component = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable(); + + component["openUpgradeDialog"](); + expect(openSpy).toHaveBeenCalledTimes(1); + expect(openSpy).toHaveBeenCalledWith(dialogService); + }); + + it("isBrowserAutofillSettingOverridden$ emits the value from the AutofillBrowserSettingsService", async () => { + pushActiveAccount(); + + mockAutofillSettings.isBrowserAutofillSettingOverridden.mockResolvedValue(true); + + const fixture = TestBed.createComponent(SettingsV2Component); + const component = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable(); + + const value = await firstValueFrom(component["isBrowserAutofillSettingOverridden$"]); + expect(value).toBe(true); + + mockAutofillSettings.isBrowserAutofillSettingOverridden.mockResolvedValue(false); + + const fixture2 = TestBed.createComponent(SettingsV2Component); + const component2 = fixture2.componentInstance; + fixture2.detectChanges(); + await fixture2.whenStable(); + + const value2 = await firstValueFrom(component2["isBrowserAutofillSettingOverridden$"]); + expect(value2).toBe(false); + }); + + it("showAutofillBadge$ emits true when default autofill is NOT disabled and nudge is true", async () => { + pushActiveAccount(); + + mockNudges.showNudgeBadge$.mockImplementation((type: NudgeType) => + of(type === NudgeType.AutofillNudge), + ); + + const fixture = TestBed.createComponent(SettingsV2Component); + const component = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable(); + + mockAutofillSettings.defaultBrowserAutofillDisabled$.next(false); + + const value = await firstValueFrom(component.showAutofillBadge$); + expect(value).toBe(true); + }); + + it("showAutofillBadge$ emits false when default autofill IS disabled even if nudge is true", async () => { + pushActiveAccount(); + + mockNudges.showNudgeBadge$.mockImplementation((type: NudgeType) => + of(type === NudgeType.AutofillNudge), + ); + + const fixture = TestBed.createComponent(SettingsV2Component); + const component = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable(); + + mockAutofillSettings.defaultBrowserAutofillDisabled$.next(true); + + const value = await firstValueFrom(component.showAutofillBadge$); + expect(value).toBe(false); + }); + + it("dismissBadge dismisses when showVaultBadge$ emits true", async () => { + const acct = pushActiveAccount(); + + mockNudges.showNudgeBadge$.mockImplementation((type: NudgeType) => { + return of(type === NudgeType.EmptyVaultNudge); + }); + + const fixture = TestBed.createComponent(SettingsV2Component); + const component = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable(); + + await component.dismissBadge(NudgeType.EmptyVaultNudge); + + expect(mockNudges.dismissNudge).toHaveBeenCalledTimes(1); + expect(mockNudges.dismissNudge).toHaveBeenCalledWith(NudgeType.EmptyVaultNudge, acct.id, true); + }); + + it("dismissBadge does nothing when showVaultBadge$ emits false", async () => { + pushActiveAccount(); + + mockNudges.showNudgeBadge$.mockReturnValue(of(false)); + + const fixture = TestBed.createComponent(SettingsV2Component); + const component = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable(); + + await component.dismissBadge(NudgeType.EmptyVaultNudge); + + expect(mockNudges.dismissNudge).not.toHaveBeenCalled(); + }); + + it("showDownloadBitwardenNudge$ proxies to nudges service for the active account", async () => { + const acct = pushActiveAccount("user-xyz"); + + mockNudges.showNudgeBadge$.mockImplementation((type: NudgeType) => + of(type === NudgeType.DownloadBitwarden), + ); + + const fixture = TestBed.createComponent(SettingsV2Component); + const component = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable(); + + const val = await firstValueFrom(component.showDownloadBitwardenNudge$); + expect(val).toBe(true); + expect(mockNudges.showNudgeBadge$).toHaveBeenCalledWith(NudgeType.DownloadBitwarden, acct.id); + }); +}); 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 1c370381f54..95aeeb2f480 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.ts @@ -1,21 +1,31 @@ import { CommonModule } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; import { RouterModule } from "@angular/router"; import { combineLatest, filter, firstValueFrom, + from, map, Observable, shareReplay, switchMap, } from "rxjs"; +import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components"; 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 { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { UserId } from "@bitwarden/common/types/guid"; -import { BadgeComponent, ItemModule } from "@bitwarden/components"; +import { + BadgeComponent, + DialogService, + ItemModule, + LinkModule, + TypographyModule, +} from "@bitwarden/components"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service"; @@ -24,8 +34,6 @@ 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(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ templateUrl: "settings-v2.component.html", imports: [ @@ -38,18 +46,30 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co ItemModule, CurrentAccountComponent, BadgeComponent, + SpotlightComponent, + TypographyModule, + LinkModule, ], + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SettingsV2Component implements OnInit { +export class SettingsV2Component { NudgeType = NudgeType; - activeUserId: UserId | null = null; - protected isBrowserAutofillSettingOverridden = false; + + protected isBrowserAutofillSettingOverridden$ = from( + this.autofillBrowserSettingsService.isBrowserAutofillSettingOverridden( + BrowserApi.getBrowserClientVendor(window), + ), + ); private authenticatedAccount$: Observable = this.accountService.activeAccount$.pipe( filter((account): account is Account => account !== null), shareReplay({ bufferSize: 1, refCount: true }), ); + protected hasPremium$ = this.authenticatedAccount$.pipe( + switchMap((account) => this.accountProfileStateService.hasPremiumFromAnySource$(account.id)), + ); + showDownloadBitwardenNudge$: Observable = this.authenticatedAccount$.pipe( switchMap((account) => this.nudgesService.showNudgeBadge$(NudgeType.DownloadBitwarden, account.id), @@ -79,13 +99,12 @@ export class SettingsV2Component implements OnInit { private readonly nudgesService: NudgesService, private readonly accountService: AccountService, private readonly autofillBrowserSettingsService: AutofillBrowserSettingsService, + private readonly accountProfileStateService: BillingAccountProfileStateService, + private readonly dialogService: DialogService, ) {} - async ngOnInit() { - this.isBrowserAutofillSettingOverridden = - await this.autofillBrowserSettingsService.isBrowserAutofillSettingOverridden( - BrowserApi.getBrowserClientVendor(window), - ); + protected openUpgradeDialog() { + PremiumUpgradeDialogComponent.open(this.dialogService); } async dismissBadge(type: NudgeType) { diff --git a/apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.html b/apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.html index a2d01ce752e..a8ed75b5de6 100644 --- a/apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.html +++ b/apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.html @@ -6,12 +6,6 @@ - - - {{ "premiumMembership" | i18n }} - - - ; protected familySponsorshipAvailable$: Observable; protected isFreeFamilyPolicyEnabled$: Observable; protected hasSingleEnterpriseOrg$: Observable; constructor( private dialogService: DialogService, - private billingAccountProfileStateService: BillingAccountProfileStateService, private environmentService: EnvironmentService, private organizationService: OrganizationService, private familiesPolicyService: FamiliesPolicyService, @@ -48,13 +45,6 @@ export class MoreFromBitwardenPageV2Component { this.familySponsorshipAvailable$ = getUserId(this.accountService.activeAccount$).pipe( switchMap((userId) => this.organizationService.familySponsorshipAvailable$(userId)), ); - this.canAccessPremium$ = this.accountService.activeAccount$.pipe( - switchMap((account) => - account - ? this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id) - : of(false), - ), - ); this.hasSingleEnterpriseOrg$ = this.familiesPolicyService.hasSingleEnterpriseOrg$(); this.isFreeFamilyPolicyEnabled$ = this.familiesPolicyService.isFreeFamilyPolicyEnabled$(); } diff --git a/libs/angular/src/vault/components/spotlight/spotlight.component.html b/libs/angular/src/vault/components/spotlight/spotlight.component.html index 720bf5c1908..92b88eb967d 100644 --- a/libs/angular/src/vault/components/spotlight/spotlight.component.html +++ b/libs/angular/src/vault/components/spotlight/spotlight.component.html @@ -3,20 +3,20 @@ >
-

{{ title }}

+

{{ title() }}

- +
diff --git a/libs/angular/src/vault/components/spotlight/spotlight.component.spec.ts b/libs/angular/src/vault/components/spotlight/spotlight.component.spec.ts new file mode 100644 index 00000000000..3d4d35fdf63 --- /dev/null +++ b/libs/angular/src/vault/components/spotlight/spotlight.component.spec.ts @@ -0,0 +1,208 @@ +import { CommonModule } from "@angular/common"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +import { SpotlightComponent } from "./spotlight.component"; + +describe("SpotlightComponent", () => { + let fixture: ComponentFixture; + let component: SpotlightComponent; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SpotlightComponent], + providers: [{ provide: I18nService, useValue: { t: (key: string) => key } }], + }).compileComponents(); + + fixture = TestBed.createComponent(SpotlightComponent); + component = fixture.componentInstance; + }); + + function detect(): void { + fixture.detectChanges(); + } + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + describe("rendering when inputs are null", () => { + it("should render without crashing when inputs are null/undefined", () => { + // Explicitly drive the inputs to null to exercise template null branches + fixture.componentRef.setInput("title", null); + fixture.componentRef.setInput("subtitle", null); + fixture.componentRef.setInput("buttonText", null); + fixture.componentRef.setInput("buttonIcon", null); + // persistent has a default, but drive it as well for coverage sanity + fixture.componentRef.setInput("persistent", false); + + expect(() => detect()).not.toThrow(); + + const root = fixture.debugElement.nativeElement as HTMLElement; + expect(root).toBeTruthy(); + }); + }); + + describe("close button visibility based on persistent", () => { + it("should show the close button when persistent is false", () => { + fixture.componentRef.setInput("persistent", false); + detect(); + + // Assumes dismiss uses bitIconButton + const dismissButton = fixture.debugElement.query(By.css("button[bitIconButton]")); + + expect(dismissButton).toBeTruthy(); + }); + + it("should hide the close button when persistent is true", () => { + fixture.componentRef.setInput("persistent", true); + detect(); + + const dismissButton = fixture.debugElement.query(By.css("button[bitIconButton]")); + expect(dismissButton).toBeNull(); + }); + }); + + describe("event emission", () => { + it("should emit onButtonClick when CTA button is clicked", () => { + const clickSpy = jest.fn(); + component.onButtonClick.subscribe(clickSpy); + + fixture.componentRef.setInput("buttonText", "Click me"); + detect(); + + const buttonDe = fixture.debugElement.query(By.css("button[bitButton]")); + expect(buttonDe).toBeTruthy(); + + const event = new MouseEvent("click"); + buttonDe.triggerEventHandler("click", event); + + expect(clickSpy).toHaveBeenCalledTimes(1); + expect(clickSpy.mock.calls[0][0]).toBeInstanceOf(MouseEvent); + }); + + it("should emit onDismiss when close button is clicked", () => { + const dismissSpy = jest.fn(); + component.onDismiss.subscribe(dismissSpy); + + fixture.componentRef.setInput("persistent", false); + detect(); + + const dismissButton = fixture.debugElement.query(By.css("button[bitIconButton]")); + expect(dismissButton).toBeTruthy(); + + dismissButton.triggerEventHandler("click", new MouseEvent("click")); + + expect(dismissSpy).toHaveBeenCalledTimes(1); + }); + + it("handleButtonClick should emit via onButtonClick()", () => { + const clickSpy = jest.fn(); + component.onButtonClick.subscribe(clickSpy); + + const event = new MouseEvent("click"); + component.handleButtonClick(event); + + expect(clickSpy).toHaveBeenCalledTimes(1); + expect(clickSpy.mock.calls[0][0]).toBe(event); + }); + + it("handleDismiss should emit via onDismiss()", () => { + const dismissSpy = jest.fn(); + component.onDismiss.subscribe(dismissSpy); + + component.handleDismiss(); + + expect(dismissSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe("content projection behavior", () => { + @Component({ + standalone: true, + imports: [SpotlightComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + + Projected content + + `, + }) + class HostWithProjectionComponent {} + + let hostFixture: ComponentFixture; + + beforeEach(async () => { + hostFixture = TestBed.createComponent(HostWithProjectionComponent); + }); + + it("should render projected content inside the spotlight", () => { + hostFixture.detectChanges(); + + const projected = hostFixture.debugElement.query(By.css(".tw-text-sm")); + expect(projected).toBeTruthy(); + expect(projected.nativeElement.textContent.trim()).toBe("Projected content"); + }); + }); + + describe("boolean attribute transform for persistent", () => { + @Component({ + standalone: true, + imports: [CommonModule, SpotlightComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + + + + + + + + + `, + }) + class BooleanHostComponent { + mode: "bare" | "none" | "falseStr" = "bare"; + } + + let boolFixture: ComponentFixture; + let boolHost: BooleanHostComponent; + + beforeEach(async () => { + boolFixture = TestBed.createComponent(BooleanHostComponent); + boolHost = boolFixture.componentInstance; + }); + + function getSpotlight(): SpotlightComponent { + const de = boolFixture.debugElement.query(By.directive(SpotlightComponent)); + return de.componentInstance as SpotlightComponent; + } + + it("treats bare 'persistent' attribute as true via booleanAttribute", () => { + boolHost.mode = "bare"; + boolFixture.detectChanges(); + + const spotlight = getSpotlight(); + expect(spotlight.persistent()).toBe(true); + }); + + it("uses default false when 'persistent' is omitted", () => { + boolHost.mode = "none"; + boolFixture.detectChanges(); + + const spotlight = getSpotlight(); + expect(spotlight.persistent()).toBe(false); + }); + + it('treats persistent="false" as false', () => { + boolHost.mode = "falseStr"; + boolFixture.detectChanges(); + + const spotlight = getSpotlight(); + expect(spotlight.persistent()).toBe(false); + }); + }); +}); diff --git a/libs/angular/src/vault/components/spotlight/spotlight.component.ts b/libs/angular/src/vault/components/spotlight/spotlight.component.ts index a912e4ce11b..1b75e1ee737 100644 --- a/libs/angular/src/vault/components/spotlight/spotlight.component.ts +++ b/libs/angular/src/vault/components/spotlight/spotlight.component.ts @@ -1,43 +1,28 @@ import { CommonModule } from "@angular/common"; -import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { booleanAttribute, ChangeDetectionStrategy, Component, input, output } from "@angular/core"; import { ButtonModule, IconButtonModule, TypographyModule } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-spotlight", templateUrl: "spotlight.component.html", imports: [ButtonModule, CommonModule, IconButtonModule, I18nPipe, TypographyModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class SpotlightComponent { // The title of the component - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input({ required: true }) title: string | null = null; + readonly title = input(); // The subtitle of the component - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() subtitle?: string | null = null; + readonly subtitle = input(); // The text to display on the button - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() buttonText?: string; - // Wheter the component can be dismissed, if true, the component will not show a close button - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() persistent = false; + readonly buttonText = input(); + // Whether the component can be dismissed, if true, the component will not show a close button + readonly persistent = input(false, { transform: booleanAttribute }); // Optional icon to display on the button - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() buttonIcon: string | null = null; - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref - @Output() onDismiss = new EventEmitter(); - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref - @Output() onButtonClick = new EventEmitter(); + readonly buttonIcon = input(); + readonly onDismiss = output(); + readonly onButtonClick = output(); handleButtonClick(event: MouseEvent): void { this.onButtonClick.emit(event); From e6d6f8d266d325289229b8ce139919aa38ba042f Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 25 Nov 2025 11:11:21 +0100 Subject: [PATCH 10/13] Migrate org reports to standalone and remove from loose components (#15791) --- .../exposed-passwords-report.component.ts | 6 +++++- .../inactive-two-factor-report.component.ts | 6 +++++- .../reused-passwords-report.component.ts | 6 +++++- .../unsecured-websites-report.component.ts | 6 +++++- .../weak-passwords-report.component.ts | 6 +++++- .../web/src/app/shared/loose-components.module.ts | 15 --------------- 6 files changed, 25 insertions(+), 20 deletions(-) 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 4dbd31ce4dc..f83614557bd 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 @@ -19,6 +19,10 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; import { PasswordRepromptService, CipherFormConfigService } from "@bitwarden/vault"; +import { HeaderModule } from "../../../../layouts/header/header.module"; +import { SharedModule } from "../../../../shared"; +import { OrganizationBadgeModule } from "../../../../vault/individual-vault/organization-badge/organization-badge.module"; +import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module"; import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service"; import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service"; import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service"; @@ -38,7 +42,7 @@ import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], - standalone: false, + imports: [SharedModule, HeaderModule, OrganizationBadgeModule, PipesModule], }) 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 17555e617cb..b1adbd26eb3 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 @@ -14,6 +14,10 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; +import { HeaderModule } from "../../../../layouts/header/header.module"; +import { SharedModule } from "../../../../shared"; +import { OrganizationBadgeModule } from "../../../../vault/individual-vault/organization-badge/organization-badge.module"; +import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module"; import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service"; import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service"; import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service"; @@ -32,7 +36,7 @@ import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponen RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], - standalone: false, + imports: [SharedModule, HeaderModule, OrganizationBadgeModule, PipesModule], }) 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 5e457a91bd9..3944e2edfcb 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 @@ -18,6 +18,10 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; +import { HeaderModule } from "../../../../layouts/header/header.module"; +import { SharedModule } from "../../../../shared"; +import { OrganizationBadgeModule } from "../../../../vault/individual-vault/organization-badge/organization-badge.module"; +import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module"; import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service"; import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service"; import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service"; @@ -37,7 +41,7 @@ import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent } RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], - standalone: false, + imports: [SharedModule, HeaderModule, OrganizationBadgeModule, PipesModule], }) 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 24f514d551f..d49baa5d465 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 @@ -18,6 +18,10 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; +import { HeaderModule } from "../../../../layouts/header/header.module"; +import { SharedModule } from "../../../../shared"; +import { OrganizationBadgeModule } from "../../../../vault/individual-vault/organization-badge/organization-badge.module"; +import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module"; import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service"; import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service"; import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service"; @@ -37,7 +41,7 @@ import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponen RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], - standalone: false, + imports: [SharedModule, HeaderModule, OrganizationBadgeModule, PipesModule], }) 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 50c18d1da3b..5158416dd28 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 @@ -19,6 +19,10 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; +import { HeaderModule } from "../../../../layouts/header/header.module"; +import { SharedModule } from "../../../../shared"; +import { OrganizationBadgeModule } from "../../../../vault/individual-vault/organization-badge/organization-badge.module"; +import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module"; import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service"; import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service"; import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service"; @@ -38,7 +42,7 @@ import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], - standalone: false, + imports: [SharedModule, HeaderModule, OrganizationBadgeModule, PipesModule], }) export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportComponent diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index f7f3aa3bfee..0fff13f428c 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -7,16 +7,6 @@ import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.comp import { FreeBitwardenFamiliesComponent } from "../billing/members/free-bitwarden-families.component"; import { SponsoredFamiliesComponent } from "../billing/settings/sponsored-families.component"; import { SponsoringOrgRowComponent } from "../billing/settings/sponsoring-org-row.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 -import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "../dirt/reports/pages/organizations/inactive-two-factor-report.component"; -// eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module -import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "../dirt/reports/pages/organizations/reused-passwords-report.component"; -// eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module -import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../dirt/reports/pages/organizations/unsecured-websites-report.component"; -// eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module -import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../dirt/reports/pages/organizations/weak-passwords-report.component"; import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import { HeaderModule } from "../layouts/header/header.module"; import { OrganizationBadgeModule } from "../vault/individual-vault/organization-badge/organization-badge.module"; @@ -29,11 +19,6 @@ import { SharedModule } from "./shared.module"; @NgModule({ imports: [SharedModule, HeaderModule, OrganizationBadgeModule, PipesModule], declarations: [ - OrgExposedPasswordsReportComponent, - OrgInactiveTwoFactorReportComponent, - OrgReusedPasswordsReportComponent, - OrgUnsecuredWebsitesReportComponent, - OrgWeakPasswordsReportComponent, RecoverDeleteComponent, RecoverTwoFactorComponent, RemovePasswordComponent, From 86a757119c31e414f1376c2dd8d1d80e63aaad13 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:07:02 +0100 Subject: [PATCH 11/13] [deps] Architecture: Update @eslint/compat to v2 (#17622) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- package-lock.json | 28 ++++++++++++++++++++++------ package.json | 2 +- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9ec580e3a5a..39256cdbb97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -84,7 +84,7 @@ "@compodoc/compodoc": "1.1.26", "@electron/notarize": "3.0.1", "@electron/rebuild": "4.0.1", - "@eslint/compat": "1.2.9", + "@eslint/compat": "2.0.0", "@lit-labs/signals": "0.1.2", "@ngtools/webpack": "19.2.14", "@storybook/addon-a11y": "8.6.12", @@ -6559,16 +6559,19 @@ } }, "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==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.0.tgz", + "integrity": "sha512-T9AfE1G1uv4wwq94ozgTGio5EUQBqAVe1X9qsQtSNVEYW6j3hvtZVm8Smr4qL1qDPFg+lOB2cL5RxTRMzq4CTA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.0.0" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "peerDependencies": { - "eslint": "^9.10.0" + "eslint": "^8.40 || 9" }, "peerDependenciesMeta": { "eslint": { @@ -6576,6 +6579,19 @@ } } }, + "node_modules/@eslint/compat/node_modules/@eslint/core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.0.0.tgz", + "integrity": "sha512-PRfWP+8FOldvbApr6xL7mNCw4cJcSTq4GA7tYbgq15mRb0kWKO/wEB2jr+uwjFH3sZvEZneZyCUGTxsv4Sahyw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, "node_modules/@eslint/config-array": { "version": "0.20.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", diff --git a/package.json b/package.json index 87a78a30796..337a3caa3bc 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@compodoc/compodoc": "1.1.26", "@electron/notarize": "3.0.1", "@electron/rebuild": "4.0.1", - "@eslint/compat": "1.2.9", + "@eslint/compat": "2.0.0", "@lit-labs/signals": "0.1.2", "@ngtools/webpack": "19.2.14", "@storybook/addon-a11y": "8.6.12", From 9e90e72961663c7042e5b14165db921ad6afc874 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 25 Nov 2025 14:48:25 +0100 Subject: [PATCH 12/13] [PM-27530] Rename BitwardenClient to PasswordManagerClient (#17578) * fix: compilation issues with PM client rename * fix: jest compilation * feat: rename all non-breaking platform instances * feat: update SDK --- libs/common/spec/jest-sdk-client-factory.ts | 6 ++--- .../abstractions/sdk/sdk-client-factory.ts | 10 ++++----- .../platform/abstractions/sdk/sdk.service.ts | 8 +++---- .../sdk/default-sdk-client-factory.ts | 12 +++++----- .../services/sdk/default-sdk.service.spec.ts | 18 +++++++-------- .../services/sdk/default-sdk.service.ts | 22 +++++++++---------- .../services/sdk/noop-sdk-client-factory.ts | 6 ++--- .../src/platform/spec/mock-sdk.service.ts | 14 ++++++------ package-lock.json | 16 +++++++------- package.json | 4 ++-- 10 files changed, 58 insertions(+), 58 deletions(-) diff --git a/libs/common/spec/jest-sdk-client-factory.ts b/libs/common/spec/jest-sdk-client-factory.ts index 8e5e1c9d3fc..8b93ba791b3 100644 --- a/libs/common/spec/jest-sdk-client-factory.ts +++ b/libs/common/spec/jest-sdk-client-factory.ts @@ -1,11 +1,11 @@ -import { BitwardenClient } from "@bitwarden/sdk-internal"; +import { PasswordManagerClient } from "@bitwarden/sdk-internal"; import { SdkClientFactory } from "../src/platform/abstractions/sdk/sdk-client-factory"; export class DefaultSdkClientFactory implements SdkClientFactory { createSdkClient( - ...args: ConstructorParameters - ): Promise { + ...args: ConstructorParameters + ): Promise { throw new Error("Method not implemented."); } } diff --git a/libs/common/src/platform/abstractions/sdk/sdk-client-factory.ts b/libs/common/src/platform/abstractions/sdk/sdk-client-factory.ts index 6a1b7b67b42..35830e42f16 100644 --- a/libs/common/src/platform/abstractions/sdk/sdk-client-factory.ts +++ b/libs/common/src/platform/abstractions/sdk/sdk-client-factory.ts @@ -1,14 +1,14 @@ -import type { BitwardenClient } from "@bitwarden/sdk-internal"; +import type { PasswordManagerClient } from "@bitwarden/sdk-internal"; /** * Factory for creating SDK clients. */ export abstract class SdkClientFactory { /** - * Creates a new BitwardenClient. Assumes the SDK is already loaded. - * @param args Bitwarden client constructor parameters + * Creates a new Password Manager client. Assumes the SDK is already loaded. + * @param args Password Manager client constructor parameters */ abstract createSdkClient( - ...args: ConstructorParameters - ): Promise; + ...args: ConstructorParameters + ): Promise; } diff --git a/libs/common/src/platform/abstractions/sdk/sdk.service.ts b/libs/common/src/platform/abstractions/sdk/sdk.service.ts index 03baec5cc37..9b7f32a8a0e 100644 --- a/libs/common/src/platform/abstractions/sdk/sdk.service.ts +++ b/libs/common/src/platform/abstractions/sdk/sdk.service.ts @@ -1,6 +1,6 @@ import { Observable } from "rxjs"; -import { BitwardenClient, Uuid } from "@bitwarden/sdk-internal"; +import { PasswordManagerClient, Uuid } from "@bitwarden/sdk-internal"; import { UserId } from "../../../types/guid"; import { Rc } from "../../misc/reference-counting/rc"; @@ -46,7 +46,7 @@ export abstract class SdkService { * Retrieve a client initialized without a user. * This client can only be used for operations that don't require a user context. */ - abstract client$: Observable; + abstract client$: Observable; /** * Retrieve a client initialized for a specific user. @@ -64,7 +64,7 @@ export abstract class SdkService { * * @param userId The user id for which to retrieve the client */ - abstract userClient$(userId: UserId): Observable>; + abstract userClient$(userId: UserId): Observable>; /** * This method is used during/after an authentication procedure to set a new client for a specific user. @@ -75,5 +75,5 @@ export abstract class SdkService { * @param userId The user id for which to set the client * @param client The client to set for the user. If undefined, the client will be unset. */ - abstract setClient(userId: UserId, client: BitwardenClient | undefined): void; + abstract setClient(userId: UserId, client: PasswordManagerClient | undefined): void; } diff --git a/libs/common/src/platform/services/sdk/default-sdk-client-factory.ts b/libs/common/src/platform/services/sdk/default-sdk-client-factory.ts index fc55cc83ac8..d0e4b96ba89 100644 --- a/libs/common/src/platform/services/sdk/default-sdk-client-factory.ts +++ b/libs/common/src/platform/services/sdk/default-sdk-client-factory.ts @@ -7,13 +7,13 @@ import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; */ export class DefaultSdkClientFactory implements SdkClientFactory { /** - * Initializes a Bitwarden client. Assumes the SDK is already loaded. - * @param args Bitwarden client constructor parameters - * @returns A BitwardenClient + * Initializes a Password Manager client. Assumes the SDK is already loaded. + * @param args Password Manager client constructor parameters + * @returns A PasswordManagerClient */ async createSdkClient( - ...args: ConstructorParameters - ): Promise { - return Promise.resolve(new sdk.BitwardenClient(...args)); + ...args: ConstructorParameters + ): Promise { + return Promise.resolve(new sdk.PasswordManagerClient(...args)); } } 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 769e8521d88..dc945594079 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 @@ -5,7 +5,7 @@ import { SecurityStateService } from "@bitwarden/common/key-management/security- // 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"; +import { PasswordManagerClient } from "@bitwarden/sdk-internal"; import { ObservableTracker, @@ -109,7 +109,7 @@ describe("DefaultSdkService", () => { }); describe("given no client override has been set for the user", () => { - let mockClient!: MockProxy; + let mockClient!: MockProxy; beforeEach(() => { mockClient = createMockClient(); @@ -123,8 +123,8 @@ describe("DefaultSdkService", () => { }); it("does not create an SDK client when called the second time with same userId", async () => { - const subject_1 = new BehaviorSubject | undefined>(undefined); - const subject_2 = new BehaviorSubject | undefined>(undefined); + const subject_1 = new BehaviorSubject | undefined>(undefined); + const subject_2 = new BehaviorSubject | undefined>(undefined); // Use subjects to ensure the subscription is kept alive service.userClient$(userId).subscribe(subject_1); @@ -139,8 +139,8 @@ describe("DefaultSdkService", () => { }); it("destroys the internal SDK client when all subscriptions are closed", async () => { - const subject_1 = new BehaviorSubject | undefined>(undefined); - const subject_2 = new BehaviorSubject | undefined>(undefined); + const subject_1 = new BehaviorSubject | undefined>(undefined); + const subject_2 = new BehaviorSubject | undefined>(undefined); const subscription_1 = service.userClient$(userId).subscribe(subject_1); const subscription_2 = service.userClient$(userId).subscribe(subject_2); await new Promise(process.nextTick); @@ -170,7 +170,7 @@ describe("DefaultSdkService", () => { describe("given overrides are used", () => { it("does not create a new client and emits the override client when a client override has already been set ", async () => { - const mockClient = mock(); + const mockClient = mock(); service.setClient(userId, mockClient); const userClientTracker = new ObservableTracker(service.userClient$(userId), false); await userClientTracker.pauseUntilReceived(1); @@ -242,8 +242,8 @@ describe("DefaultSdkService", () => { }); }); -function createMockClient(): MockProxy { - const client = mock(); +function createMockClient(): MockProxy { + const client = mock(); client.crypto.mockReturnValue(mock()); client.platform.mockReturnValue({ state: jest.fn().mockReturnValue(mock()), 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 6f9c9df761c..eb663c6f928 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -20,7 +20,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co // eslint-disable-next-line no-restricted-imports import { KeyService, KdfConfigService, KdfConfig, KdfType } from "@bitwarden/key-management"; import { - BitwardenClient, + PasswordManagerClient, ClientSettings, DeviceType as SdkDeviceType, TokenProvider, @@ -70,9 +70,9 @@ class JsTokenProvider implements TokenProvider { export class DefaultSdkService implements SdkService { private sdkClientOverrides = new BehaviorSubject<{ - [userId: UserId]: Rc | typeof UnsetClient; + [userId: UserId]: Rc | typeof UnsetClient; }>({}); - private sdkClientCache = new Map>>(); + private sdkClientCache = new Map>>(); client$ = this.environmentService.environment$.pipe( concatMap(async (env) => { @@ -107,14 +107,14 @@ export class DefaultSdkService implements SdkService { private userAgent: string | null = null, ) {} - userClient$(userId: UserId): Observable> { + userClient$(userId: UserId): Observable> { return this.sdkClientOverrides.pipe( takeWhile((clients) => clients[userId] !== UnsetClient, false), map((clients) => { if (clients[userId] === UnsetClient) { throw new Error("Encountered UnsetClient even though it should have been filtered out"); } - return clients[userId] as Rc; + return clients[userId] as Rc; }), distinctUntilChanged(), switchMap((clientOverride) => { @@ -129,7 +129,7 @@ export class DefaultSdkService implements SdkService { ); } - setClient(userId: UserId, client: BitwardenClient | undefined) { + setClient(userId: UserId, client: PasswordManagerClient | undefined) { const previousValue = this.sdkClientOverrides.value[userId]; this.sdkClientOverrides.next({ @@ -149,7 +149,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> { + private internalClient$(userId: UserId): Observable> { const cached = this.sdkClientCache.get(userId); if (cached !== undefined) { return cached; @@ -187,7 +187,7 @@ export class DefaultSdkService implements SdkService { switchMap( ([env, account, kdfParams, privateKey, userKey, signingKey, orgKeys, securityState]) => { // Create our own observable to be able to implement clean-up logic - return new Observable>((subscriber) => { + return new Observable>((subscriber) => { const createAndInitializeClient = async () => { if (env == null || kdfParams == null || privateKey == null || userKey == null) { return undefined; @@ -214,7 +214,7 @@ export class DefaultSdkService implements SdkService { return client; }; - let client: Rc | undefined; + let client: Rc | undefined; createAndInitializeClient() .then((c) => { client = c === undefined ? undefined : new Rc(c); @@ -239,7 +239,7 @@ export class DefaultSdkService implements SdkService { private async initializeClient( userId: UserId, - client: BitwardenClient, + client: PasswordManagerClient, account: AccountInfo, kdfParams: KdfConfig, privateKey: EncryptedString, @@ -281,7 +281,7 @@ export class DefaultSdkService implements SdkService { await this.loadFeatureFlags(client); } - private async loadFeatureFlags(client: BitwardenClient) { + private async loadFeatureFlags(client: PasswordManagerClient) { const serverConfig = await firstValueFrom(this.configService.serverConfig$); const featureFlagMap = new Map( diff --git a/libs/common/src/platform/services/sdk/noop-sdk-client-factory.ts b/libs/common/src/platform/services/sdk/noop-sdk-client-factory.ts index d7eab7e8dc9..8ed0bc276cc 100644 --- a/libs/common/src/platform/services/sdk/noop-sdk-client-factory.ts +++ b/libs/common/src/platform/services/sdk/noop-sdk-client-factory.ts @@ -1,4 +1,4 @@ -import type { BitwardenClient } from "@bitwarden/sdk-internal"; +import type { PasswordManagerClient } from "@bitwarden/sdk-internal"; import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; @@ -9,8 +9,8 @@ import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; */ export class NoopSdkClientFactory implements SdkClientFactory { createSdkClient( - ...args: ConstructorParameters - ): Promise { + ...args: ConstructorParameters + ): Promise { return Promise.reject(new Error("SDK not available")); } } diff --git a/libs/common/src/platform/spec/mock-sdk.service.ts b/libs/common/src/platform/spec/mock-sdk.service.ts index 66a6ab3ec84..aec2438c853 100644 --- a/libs/common/src/platform/spec/mock-sdk.service.ts +++ b/libs/common/src/platform/spec/mock-sdk.service.ts @@ -7,7 +7,7 @@ import { throwIfEmpty, } from "rxjs"; -import { BitwardenClient } from "@bitwarden/sdk-internal"; +import { PasswordManagerClient } from "@bitwarden/sdk-internal"; import { UserId } from "../../types/guid"; import { SdkService, UserNotLoggedInError } from "../abstractions/sdk/sdk.service"; @@ -17,18 +17,18 @@ import { DeepMockProxy, mockDeep } from "./mock-deep"; export class MockSdkService implements SdkService { private userClients$ = new BehaviorSubject<{ - [userId: UserId]: Rc | undefined; + [userId: UserId]: Rc | undefined; }>({}); - private _client$ = new BehaviorSubject(mockDeep()); + private _client$ = new BehaviorSubject(mockDeep()); client$ = this._client$.asObservable(); version$ = new BehaviorSubject("0.0.1-test").asObservable(); - userClient$(userId: UserId): Observable> { + userClient$(userId: UserId): Observable> { return this.userClients$.pipe( takeWhile((clients) => clients[userId] !== undefined, false), - map((clients) => clients[userId] as Rc), + map((clients) => clients[userId] as Rc), distinctUntilChanged(), throwIfEmpty(() => new UserNotLoggedInError(userId)), ); @@ -42,7 +42,7 @@ export class MockSdkService implements SdkService { * Returns the non-user scoped client mock. * This is what is returned by the `client$` observable. */ - get client(): DeepMockProxy { + get client(): DeepMockProxy { return this._client$.value; } @@ -55,7 +55,7 @@ export class MockSdkService implements SdkService { * @returns A user-scoped mock for the user. */ userLogin: (userId: UserId) => { - const client = mockDeep(); + const client = mockDeep(); this.userClients$.next({ ...this.userClients$.getValue(), [userId]: new Rc(client), diff --git a/package-lock.json b/package-lock.json index 39256cdbb97..1e2b8119a95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,8 +23,8 @@ "@angular/platform-browser": "19.2.14", "@angular/platform-browser-dynamic": "19.2.14", "@angular/router": "19.2.14", - "@bitwarden/commercial-sdk-internal": "0.2.0-main.375", - "@bitwarden/sdk-internal": "0.2.0-main.375", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.395", + "@bitwarden/sdk-internal": "0.2.0-main.395", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", @@ -4620,9 +4620,9 @@ "link": true }, "node_modules/@bitwarden/commercial-sdk-internal": { - "version": "0.2.0-main.375", - "resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.375.tgz", - "integrity": "sha512-UMVfLjMh79+5et1if7qqOi+pSGP5Ay3AcGp4E5oLZ0p0yFsN2Q54UFv+SLju0/oI0qTvVZP1RkEtTJXHdNrpTg==", + "version": "0.2.0-main.395", + "resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.395.tgz", + "integrity": "sha512-DrxL3iA29hzWpyxPyZjiXx0m+EHOgk4CVb+BAi2SoxsacmyHYuTgXuASFMieRz2rv85wS3UR0N64Ok9lC+xNYA==", "license": "BITWARDEN SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT", "dependencies": { "type-fest": "^4.41.0" @@ -4725,9 +4725,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.2.0-main.375", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.375.tgz", - "integrity": "sha512-kf2SKFkAdSmV2/ORo6u1eegwYW2ha62NHUsx2ij2uPWmm7mzXUoNa7z8mqhJV1ozg5o7yBqBuXd6Wqo9Ww+/RA==", + "version": "0.2.0-main.395", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.395.tgz", + "integrity": "sha512-biExeL2Grp11VQjjK6QM16+WOYk87mTgUhYKFm+Bu/A0zZBzhL/6AocpA9h2T5M8rLCGVVJVUMaXUW3YrSTqEA==", "license": "GPL-3.0", "dependencies": { "type-fest": "^4.41.0" diff --git a/package.json b/package.json index 337a3caa3bc..2a06b80f007 100644 --- a/package.json +++ b/package.json @@ -160,8 +160,8 @@ "@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.375", - "@bitwarden/commercial-sdk-internal": "0.2.0-main.375", + "@bitwarden/sdk-internal": "0.2.0-main.395", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.395", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", From cdd8a697e8687fb14c2d7899c45919ce481eccac Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 25 Nov 2025 08:41:41 -0600 Subject: [PATCH 13/13] do not show copy password button on the web for users that do not have access (#17635) --- .../vault-cipher-row.component.html | 10 +- .../vault-cipher-row.component.spec.ts | 144 ++++++++++++++++++ 2 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.spec.ts diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html index c09553dab9c..c8732154ef4 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html @@ -109,10 +109,12 @@ {{ "copyUsername" | i18n }} - + @if (cipher.viewPassword) { + + }